From 776e312e8843971ae4e1212e2aee4e7348057cbf Mon Sep 17 00:00:00 2001
From: Bye <bye@byecorps.com>
Date: Sat, 2 Sep 2023 20:25:16 +0100
Subject: [PATCH] a lot of things have changed, but i really need to fix this
 sill thing.

---
 README.md                         |    9 +
 assets/Sprite-0002-asoutline.ase  |  Bin 0 -> 1315 bytes
 assets/Sprite-0002-asoutline.png  |  Bin 0 -> 1064 bytes
 assets/Sprite-0002.ase            |  Bin 868 -> 1016 bytes
 package-lock.json                 | 1025 +++++++++++++++++++++++++++--
 package.json                      |    8 +-
 scripts/convertImagesToBase256.js |   22 +
 src/img/catapult.webp             |  Bin 184 -> 0 bytes
 src/index.html                    |   24 +-
 src/js/canvas.js                  |  106 ++-
 src/js/config.js                  |    2 +-
 src/js/game.js                    |  151 ++++-
 src/js/inputs/mouse.js            |    4 +
 src/js/objects.js                 |   22 +-
 src/js/text.js                    |    2 +-
 src/js/utils.js                   |   75 ++-
 16 files changed, 1312 insertions(+), 138 deletions(-)
 create mode 100644 assets/Sprite-0002-asoutline.ase
 create mode 100644 assets/Sprite-0002-asoutline.png
 create mode 100644 scripts/convertImagesToBase256.js
 delete mode 100644 src/img/catapult.webp

diff --git a/README.md b/README.md
index 2545898..81c8b2f 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,15 @@
 
 Bye's 2023 entry for [JS13K](https://js13kgames.com).
 
+## Where to play
+
+The ONLY official places to play the game jam version of this game are:
+- https://js13kgames.com [will be updated with the entry link when submitted]
+- https://byemc.xyz/games/js13k2023
+- https://byemc.itch.io/js13k2023
+
+Any other website is unofficial and should be ignored.
+
 ## Third Party Code
 
 This project uses some code from [herebefrogs/gamejam-boilerplate](https://github.com/herebefrogs/gamejam-boilerplate).
diff --git a/assets/Sprite-0002-asoutline.ase b/assets/Sprite-0002-asoutline.ase
new file mode 100644
index 0000000000000000000000000000000000000000..2ea8719680051bec7db56aa53f78b3a60c21acdb
GIT binary patch
literal 1315
zcmY#pWng%)l$jxhL4-kpfsuiMA%y`MSTHbx1Q`Sv1dxTP#(u)Y!0_uME7(?61_lN(
z1_lOp1+bM!c4RR!Fi0v`Kt;@<G@SYmX8mVisI08~&%m%~(W3th3<iNs{}~t@CmsIJ
zz>s+PF-ZLXXOMWJ<9`MQ&qbO485jy5PXddd{?EW*k;?!QUnKw%{|r*I=rhP1i%gJs
z;35z?>otfx{TXDkXC#Q-)deD7Usm|f!0`Vw$h1mFkjUXe5cwah>;G+#Ig1QHB98+>
z<o`+okowcR{xdLa5&*GpJ0^le5;uX!U9bN$Fw9Z_`Q^4F$o-!a|Nm!Tc)STjCJKN|
zo8$=c%c4S%$YHR!1_Q`6M@NuIU=xU3v<pNkFo4W-O#BTBheaTfNtZz+B&;@V0?EF9
z4YKk7|Ns9tF)%Q&fYJdpuq!Jo7cE+35ZL56>2TuZ$BX`dHb`{zT$EY(c+#T(r!8_B
zJXZ-6es)~+d67jXL*OEYS+5yRe{S-OWa#Q*0ENouNtKQapukwf@c;IrMFtF@0H`$h
ze|py@0Rxby6F2?e^?H^<<!#5qpA-K--t<3FV9_MU&x;EGAKvs|gJGAW<Nv^>|BH70
zS76xWnD{$!(f>)8|5sL4ZrZd7<m&(b<v}i1U<M}v2mwkC><m7Mm8nGvhRqBN46F(a
z4B`x|4F8!R3K$sl7`PZJ=DeNa&F7%V!MykX(#q;3Tc+-)oH+I2W1lNKu8D12eyYaA
zG}V6Vx(|zPwpVVpY(M(rYE{v_t8cD^KlQ!O`O5mMYtK3L?>pCZJal_zUH-P>dE{~K
zjT6it-}&W#C1KC=v)O)s4*h1f$Vi<%g#nbt8Nq2Bl(Ji(PLe=zk`CBOcPCxsa!}-O
z{`-Iay6xT*ilaUG!v&P4%bo3z*!Vk3qwn0zjZ%KLPo^GwxVFe5>dU>S1tn+Z#Qi&R
z`)c^n1=p%iMjMuV+9l&t{##uC`}S`0$Kgq<_|Cc~ChgI2H|w5!H+bg$-<)Z2?}W4&
zQQaiX2ni-o2qA(gf<XinOw&#a9#-IS37!<~p{aRr?UJJ+lVWNaK8DTbOiO6%o*bJk
zFHyK>-hJjX?&$(=L)Y6acV4^r)b_8pe^$;eJ`#R0{=fe`Z5buYRi6U)ZuR^BWcF77
zO*Ojv_U`qseer$r+3UgXQ{Q*Ie_?ymX4RiX%Ef!l@7vrzyZu`E=e1wgKJxy3Qm4kY
z_S9tk=JiGLvpV(nrn`Pxdn){9`sUAbKh`Mhd{<<5=EnW1nnn9o<)!XZng43_tFWih
zZ@o)ruRlNi)d@?vm-&nLO?>PA`g_5rYtHW!b>&_Ce^mW-+ZFb=CEv|1G=FVz%lhm6
gFGSYe{P|+bz4dkW)7Ed7%m3SwuX3NC+j3ee04r_@2mk;8

literal 0
HcmV?d00001

diff --git a/assets/Sprite-0002-asoutline.png b/assets/Sprite-0002-asoutline.png
new file mode 100644
index 0000000000000000000000000000000000000000..f05f90f97fa660a898e2cd31274c3559aa73779d
GIT binary patch
literal 1064
zcmeAS@N?(olHy`uVBq!ia0y~yU@~Q3U|7$=#=yY9TCP8Zfq{XsILO_JVcj{ImkbQd
zk33x*Ln`LHy>r+1v4ey|U__vR?h+QS1%h%JEkYWKx)%h0@43ao+cmlH-JH)nKhkqb
zOgHUoXJ;S_JXmu4qI^xAZB6`Vm%OL1A6wL%jgETzFSmAYwSC^vtn-;ivtB>5eLly2
zvp)kv0{0>|1`h#K28X5<j0{5a^nN}){QGp+^Fq<%+t1%z{$cUeclTd>4eN{Ec&j*S
z<E>ql=PN5V1T!<ZbVxBMI9_33-~{R4@xu9GwTV?{(X|xs9c<@c)^5uCU|p8Q(7+PN
z%%B2SYW_2z;u?#)aLz*;u$qE-bCFE}x#G~4&O?iZtKCD@^7CrrA4u6O|9)~;VES@o
zN4oGbOi;iu9OMp=31EK^p=4g#e1`v3b+$F<E#6%}{;}{_VdAo@_qJbMH~s64m5dBR
zC`SAUs91FP_vtB*<?h=pml1z=@Y?&QYrdcTFCLbH;j{<Ni4U^2%}dBO()}Q84GUSg
zaq?XLDWBA|rRJ7czy7;o&-AIU8}5Tb6cJ1ypL|&O;oOWFT+e&`G~{ag!>aFx?T)*a
zjMc7XZt8d6)qXPAV2>H%b|P%+(n^}%zF3mbFdx%f@R$Kv2@kwwz9MFhU$*SMKm8)-
zM$v1Jx9q(y?sxn5uC?F(x4ylJ5jJ<8I6pl6`?N0K=lPFsKVR2x{d!-|tkVD6(rop9
zSFhf?zk2)kQt8y%oopCk(UK|Lntrn?>FJC6w|}dg=XzZj^>p7Fv%+ng@(Q#uJtJPL
zk)!k^HEef%_5OQ%=ik?Pce*wz_iotkIKR!o$mwhmC?ArQcgWM!!1|X}NaCXL&2w*B
PKqh#)`njxgN@xNAVE<o-

literal 0
HcmV?d00001

diff --git a/assets/Sprite-0002.ase b/assets/Sprite-0002.ase
index 6c99e8e8903b9f90eefd27e321f3034060240992..30e98ddb1793451bdc5c4e10f8bc3cac3ccda98c 100644
GIT binary patch
delta 178
zcmaFD_Jf`K2Qvf1gQZLidJJ3}x$iMCGcYhre#2BzKZSvT;nzn-h7<+{21W)3h86|}
z237?K`#%!{6zDK;F;vXCJLw{qgCd9X-~aR1ZTFr~9PP;;E}%4B?rev|#@|^QedlIw
zl=8EEGWFQQwM7<DU+z6EC^<7H?%$EySHq7kxK@2K+OXu)E*YQl-{Sh;w|ARA4o_Mo
b%6Ha1F=>yEyIJ?-yTLQ}|K?1KdnW_{#_vRw

delta 28
kcmeyt{)CM?g_(ij!BR$s28M=>-1nH6g&2e;zhSBX0Dwmbo&W#<

diff --git a/package-lock.json b/package-lock.json
index 5d8a277..551868e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,24 +1,26 @@
 {
-  "name": "gamejam-boilerplate",
-  "version": "1.2.0",
+  "name": "deilg-ei",
+  "version": "0.0.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "gamejam-boilerplate",
-      "version": "1.2.0",
+      "name": "deilg-ei",
+      "version": "0.0.0",
       "license": "MIT",
       "dependencies": {
+        "image-to-base64": "^2.2.0",
         "npm": "^9.8.0"
       },
       "devDependencies": {
         "advzip-bin": "^2.0.0",
-        "browser-sync": "^2.29.1",
+        "browser-sync": "~2.29.1",
         "chokidar-cli": "^3.0.0",
-        "esbuild": "^0.9.2",
+        "esbuild": "~0.9.2",
         "html-inline": "^1.2.0",
         "html-minifier": "^4.0.0",
         "npm-run-all": "^4.1.5",
+        "serve": "^14.2.1",
         "terser": "^5.15.1",
         "tinify": "^1.6.0-beta.2"
       }
@@ -112,9 +114,15 @@
       }
     },
     "node_modules/@types/node": {
-      "version": "20.4.10",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.10.tgz",
-      "integrity": "sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==",
+      "version": "20.5.8",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.8.tgz",
+      "integrity": "sha512-eajsR9aeljqNhK028VG0Wuw+OaY5LLxYmxeoXynIoE6jannr9/Ucd1LL0hSSoafk5LTYG+FfqsyGt81Q6Zkybw==",
+      "dev": true
+    },
+    "node_modules/@zeit/schemas": {
+      "version": "2.29.0",
+      "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz",
+      "integrity": "sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==",
       "dev": true
     },
     "node_modules/accepts": {
@@ -160,7 +168,32 @@
         "node": ">=8"
       }
     },
-    "node_modules/ansi-regex": {
+    "node_modules/ajv": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+      "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ansi-align": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+      "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^4.1.0"
+      }
+    },
+    "node_modules/ansi-align/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==",
@@ -169,6 +202,50 @@
         "node": ">=8"
       }
     },
+    "node_modules/ansi-align/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "node_modules/ansi-align/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-align/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/ansi-regex": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
     "node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -238,6 +315,12 @@
         "node": ">=4"
       }
     },
+    "node_modules/arg": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+      "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+      "dev": true
+    },
     "node_modules/array-buffer-byte-length": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
@@ -639,6 +722,40 @@
         "safe-buffer": "^5.1.1"
       }
     },
+    "node_modules/boxen": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz",
+      "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==",
+      "dev": true,
+      "dependencies": {
+        "ansi-align": "^3.0.1",
+        "camelcase": "^7.0.0",
+        "chalk": "^5.0.1",
+        "cli-boxes": "^3.0.0",
+        "string-width": "^5.1.2",
+        "type-fest": "^2.13.0",
+        "widest-line": "^4.0.1",
+        "wrap-ansi": "^8.0.1"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/boxen/node_modules/chalk": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+      "dev": true,
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
     "node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -866,12 +983,15 @@
       }
     },
     "node_modules/camelcase": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
-      "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==",
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
+      "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
       "dev": true,
       "engines": {
-        "node": ">=0.10.0"
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/camelcase-keys": {
@@ -887,6 +1007,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/camelcase-keys/node_modules/camelcase": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+      "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/caw": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz",
@@ -918,6 +1047,21 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
+    "node_modules/chalk-template": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
+      "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "^4.1.2"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk-template?sponsor=1"
+      }
+    },
     "node_modules/chokidar": {
       "version": "3.5.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -1132,6 +1276,165 @@
         "node": ">= 4.0"
       }
     },
+    "node_modules/cli-boxes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+      "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/clipboardy": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
+      "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
+      "dev": true,
+      "dependencies": {
+        "arch": "^2.2.0",
+        "execa": "^5.1.1",
+        "is-wsl": "^2.2.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/clipboardy/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/clipboardy/node_modules/execa": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "get-stream": "^6.0.0",
+        "human-signals": "^2.1.0",
+        "is-stream": "^2.0.0",
+        "merge-stream": "^2.0.0",
+        "npm-run-path": "^4.0.1",
+        "onetime": "^5.1.2",
+        "signal-exit": "^3.0.3",
+        "strip-final-newline": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/execa?sponsor=1"
+      }
+    },
+    "node_modules/clipboardy/node_modules/get-stream": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/clipboardy/node_modules/is-stream": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/clipboardy/node_modules/is-wsl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+      "dev": true,
+      "dependencies": {
+        "is-docker": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/clipboardy/node_modules/npm-run-path": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/clipboardy/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/clipboardy/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/clipboardy/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/clipboardy/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/cliui": {
       "version": "8.0.1",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -1146,6 +1449,64 @@
         "node": ">=12"
       }
     },
+    "node_modules/cliui/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/cliui/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "node_modules/cliui/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/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/cliui/node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
     "node_modules/clone-response": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
@@ -1179,6 +1540,51 @@
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
     },
+    "node_modules/compressible": {
+      "version": "2.0.18",
+      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+      "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+      "dev": true,
+      "dependencies": {
+        "mime-db": ">= 1.43.0 < 2"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/compression": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+      "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+      "dev": true,
+      "dependencies": {
+        "accepts": "~1.3.5",
+        "bytes": "3.0.0",
+        "compressible": "~2.0.16",
+        "debug": "2.6.9",
+        "on-headers": "~1.0.2",
+        "safe-buffer": "5.1.2",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/compression/node_modules/bytes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+      "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/compression/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "dev": true
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1450,6 +1856,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
     "node_modules/define-properties": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
@@ -1572,6 +1987,12 @@
       "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==",
       "dev": true
     },
+    "node_modules/eastasianwidth": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+      "dev": true
+    },
     "node_modules/easy-extender": {
       "version": "2.3.4",
       "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz",
@@ -1603,9 +2024,9 @@
       "dev": true
     },
     "node_modules/emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
       "dev": true
     },
     "node_modules/encodeurl": {
@@ -1930,6 +2351,21 @@
         "node": ">=4"
       }
     },
+    "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-url-parser": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz",
+      "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^1.3.2"
+      }
+    },
     "node_modules/fd-slicer": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -2105,9 +2541,9 @@
       }
     },
     "node_modules/fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
       "dev": true,
       "hasInstallScript": true,
       "optional": true,
@@ -2125,15 +2561,15 @@
       "dev": true
     },
     "node_modules/function.prototype.name": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
-      "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
+      "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
       "dev": true,
       "dependencies": {
         "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "es-abstract": "^1.19.0",
-        "functions-have-names": "^1.2.2"
+        "define-properties": "^1.2.0",
+        "es-abstract": "^1.22.1",
+        "functions-have-names": "^1.2.3"
       },
       "engines": {
         "node": ">= 0.4"
@@ -2654,6 +3090,15 @@
         "node": ">=8.0.0"
       }
     },
+    "node_modules/human-signals": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+      "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+      "dev": true,
+      "engines": {
+        "node": ">=10.17.0"
+      }
+    },
     "node_modules/iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -2686,6 +3131,14 @@
         }
       ]
     },
+    "node_modules/image-to-base64": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/image-to-base64/-/image-to-base64-2.2.0.tgz",
+      "integrity": "sha512-Z+aMwm/91UOQqHhrz7Upre2ytKhWejZlWV/JxUTD1sT7GWWKFDJUEV5scVQKnkzSgPHFuQBUEWcanO+ma0PSVw==",
+      "dependencies": {
+        "node-fetch": "^2.6.0"
+      }
+    },
     "node_modules/immutable": {
       "version": "3.8.2",
       "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
@@ -2860,6 +3313,21 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/is-docker": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+      "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+      "dev": true,
+      "bin": {
+        "is-docker": "cli.js"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2971,6 +3439,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-port-reachable": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz",
+      "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==",
+      "dev": true,
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-regex": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -3126,6 +3606,12 @@
       "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
       "dev": true
     },
+    "node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "dev": true
+    },
     "node_modules/jsonfile": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz",
@@ -3192,6 +3678,15 @@
         "node": ">=8.3.0"
       }
     },
+    "node_modules/localtunnel/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/localtunnel/node_modules/cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -3220,12 +3715,61 @@
         }
       }
     },
+    "node_modules/localtunnel/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
     "node_modules/localtunnel/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/localtunnel/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/localtunnel/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/localtunnel/node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
     "node_modules/localtunnel/node_modules/yargs": {
       "version": "17.1.1",
       "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz",
@@ -3437,6 +3981,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/merge-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+      "dev": true
+    },
     "node_modules/micromatch": {
       "version": "4.0.5",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@@ -3477,7 +4027,16 @@
         "mime-db": "1.52.0"
       },
       "engines": {
-        "node": ">= 0.6"
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
       }
     },
     "node_modules/mimic-response": {
@@ -3543,6 +4102,25 @@
         "lower-case": "^1.1.1"
       }
     },
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/normalize-package-data": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -6660,6 +7238,15 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -6669,6 +7256,21 @@
         "wrappy": "1"
       }
     },
+    "node_modules/onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dev": true,
+      "dependencies": {
+        "mimic-fn": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/openurl": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz",
@@ -6850,6 +7452,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==",
+      "dev": true
+    },
     "node_modules/path-key": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
@@ -6865,6 +7473,12 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
       "dev": true
     },
+    "node_modules/path-to-regexp": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz",
+      "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==",
+      "dev": true
+    },
     "node_modules/path-type": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
@@ -7016,6 +7630,12 @@
         "once": "^1.3.1"
       }
     },
+    "node_modules/punycode": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+      "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
+      "dev": true
+    },
     "node_modules/query-string": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
@@ -7054,6 +7674,30 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "dev": true,
+      "dependencies": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "bin": {
+        "rc": "cli.js"
+      }
+    },
+    "node_modules/rc/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/read-pkg": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -7260,6 +7904,28 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/registry-auth-token": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+      "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+      "dev": true,
+      "dependencies": {
+        "rc": "^1.1.6",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/registry-url": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+      "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==",
+      "dev": true,
+      "dependencies": {
+        "rc": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/relateurl": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
@@ -7290,6 +7956,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/require-from-string": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/require-main-filename": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@@ -7517,6 +8192,95 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/serve": {
+      "version": "14.2.1",
+      "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.1.tgz",
+      "integrity": "sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==",
+      "dev": true,
+      "dependencies": {
+        "@zeit/schemas": "2.29.0",
+        "ajv": "8.11.0",
+        "arg": "5.0.2",
+        "boxen": "7.0.0",
+        "chalk": "5.0.1",
+        "chalk-template": "0.4.0",
+        "clipboardy": "3.0.0",
+        "compression": "1.7.4",
+        "is-port-reachable": "4.0.0",
+        "serve-handler": "6.1.5",
+        "update-check": "1.5.4"
+      },
+      "bin": {
+        "serve": "build/main.js"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/serve-handler": {
+      "version": "6.1.5",
+      "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz",
+      "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==",
+      "dev": true,
+      "dependencies": {
+        "bytes": "3.0.0",
+        "content-disposition": "0.5.2",
+        "fast-url-parser": "1.1.3",
+        "mime-types": "2.1.18",
+        "minimatch": "3.1.2",
+        "path-is-inside": "1.0.2",
+        "path-to-regexp": "2.2.1",
+        "range-parser": "1.2.0"
+      }
+    },
+    "node_modules/serve-handler/node_modules/bytes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+      "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/serve-handler/node_modules/content-disposition": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+      "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/serve-handler/node_modules/mime-db": {
+      "version": "1.33.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+      "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/serve-handler/node_modules/mime-types": {
+      "version": "2.1.18",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+      "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+      "dev": true,
+      "dependencies": {
+        "mime-db": "~1.33.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/serve-handler/node_modules/range-parser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+      "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/serve-index": {
       "version": "1.9.1",
       "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
@@ -7595,6 +8359,18 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/serve/node_modules/chalk": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz",
+      "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==",
+      "dev": true,
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
     "node_modules/server-destroy": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
@@ -8041,17 +8817,20 @@
       "dev": true
     },
     "node_modules/string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
       "dev": true,
       "dependencies": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^9.2.2",
+        "strip-ansi": "^7.0.1"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/string.prototype.padend": {
@@ -8117,15 +8896,18 @@
       }
     },
     "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==",
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
       "dev": true,
       "dependencies": {
-        "ansi-regex": "^5.0.1"
+        "ansi-regex": "^6.0.1"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
       }
     },
     "node_modules/strip-bom": {
@@ -8155,6 +8937,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/strip-final-newline": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+      "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/strip-indent": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
@@ -8170,6 +8961,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/strip-outer": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
@@ -8247,9 +9047,9 @@
       }
     },
     "node_modules/terser": {
-      "version": "5.19.2",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
-      "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
+      "version": "5.19.3",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.3.tgz",
+      "integrity": "sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg==",
       "dev": true,
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
@@ -8353,6 +9153,11 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+    },
     "node_modules/trim-newlines": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
@@ -8434,6 +9239,18 @@
         "node": "*"
       }
     },
+    "node_modules/type-fest": {
+      "version": "2.19.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/typed-array-buffer": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
@@ -8573,12 +9390,40 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/update-check": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz",
+      "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==",
+      "dev": true,
+      "dependencies": {
+        "registry-auth-token": "3.3.2",
+        "registry-url": "3.1.0"
+      }
+    },
     "node_modules/upper-case": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
       "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==",
       "dev": true
     },
+    "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/uri-js/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/url-parse-lax": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
@@ -8644,6 +9489,20 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+    },
+    "node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
     "node_modules/which": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -8697,23 +9556,50 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/widest-line": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
+      "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/wrap-ansi": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
       "dev": true,
       "dependencies": {
-        "ansi-styles": "^4.0.0",
-        "string-width": "^4.1.0",
-        "strip-ansi": "^6.0.0"
+        "ansi-styles": "^6.1.0",
+        "string-width": "^5.0.1",
+        "strip-ansi": "^7.0.1"
       },
       "engines": {
-        "node": ">=10"
+        "node": ">=12"
       },
       "funding": {
         "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
       }
     },
+    "node_modules/wrap-ansi/node_modules/ansi-styles": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -8801,6 +9687,47 @@
         "node": ">=12"
       }
     },
+    "node_modules/yargs/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/yargs/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "node_modules/yargs/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs/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/yauzl": {
       "version": "2.10.0",
       "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
diff --git a/package.json b/package.json
index 03b9e01..0385d8b 100644
--- a/package.json
+++ b/package.json
@@ -12,8 +12,11 @@
     "build:zipSize": "node scripts/zipSize.js",
     "start": "npm-run-all -s clean -p dev:*",
     "dev:js": "esbuild src/js/game.js --bundle --format=iife --loader:.webp=dataurl --sourcemap --outfile=dist/game.js --watch",
+    "dev:inject_images": "node scripts/convertImagesToBase256.js",
+    "ndev:copy_html_index": "cp src/index.html dist/",
     "dev:serve_watch_html_js": "browser-sync dist src --watch --host 0.0.0.0 --https --open false",
-    "dev:watch_img": "chokidar src/img -d 0 -c 'npm run dev:js'"
+    "dev:serve_nowatch": "cd dist/ && serve .",
+    "dev:watch_img": "chokidar src -d 0 -c 'npm run dev:js'"
   },
   "license": "MIT",
   "devDependencies": {
@@ -28,6 +31,7 @@
     "tinify": "^1.6.0-beta.2"
   },
   "dependencies": {
+    "image-to-base64": "^2.2.0",
     "npm": "^9.8.0"
   },
   "repository": {
@@ -39,4 +43,4 @@
     "url": "https://github.com/byemc/js13k2023/issues"
   },
   "homepage": "https://github.com/byemc/js13k2023#readme"
-}
\ No newline at end of file
+}
diff --git a/scripts/convertImagesToBase256.js b/scripts/convertImagesToBase256.js
new file mode 100644
index 0000000..4c71d3c
--- /dev/null
+++ b/scripts/convertImagesToBase256.js
@@ -0,0 +1,22 @@
+const fs = require("fs");
+
+imageFormats = ["webp"];
+
+files = fs.readdirSync("src/img");
+let gameCode;
+gameCode = fs.readFileSync("dist/game.js", "utf-8");
+for (let image of files) {
+    console.log(image)
+    if (!imageFormats.includes(image.split(".")[1])) continue;
+    try {
+        const data = fs.readFileSync(`src/img/${image}`, null);
+        console.log(data.toString("base64url"));
+        gameCode = gameCode.replaceAll(image, `data:image/webp;base64,${data.toString("base64url")}`);
+    } catch (err) {
+        console.error(err);
+    }
+}
+
+// console.log(gameCode)
+
+fs.writeFileSync("dist/game.js", gameCode, "utf-8");
diff --git a/src/img/catapult.webp b/src/img/catapult.webp
deleted file mode 100644
index 12ba75241a220c23e3bc79221a45d51fd70bff06..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 184
zcmWIYbaUIlz`zjh>J$(bVBxcbfq_Ba-~g+D`ho?y(q`8-cc*B$Ey<lNEj?$!?y58O
z4F4C-=9O`oB;w{im(e4sA+%s+cccEb{i~YeIR*cQ-`JLW_{}$AVWmsHJq!3YxtuZ&
z?Jl|gPcyppmp^}Q-9pYwuHEx9n@YHM?KIJT`hWk$ZINF#zTWX_joPf#PR>0$Pn<dP
sgg0iN$ESm>-R(1GWX!&AvwNOb&hh8YQ_{6B7Tz>eo0V&LD^flU0PvJmzyJUM

diff --git a/src/index.html b/src/index.html
index ccdc641..71f8024 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,24 +1,12 @@
 <!-- maybe slightly copied from herebefrog -->
 
-<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
+<meta name="viewport" content="width=device-width,initial-scale=1">
 <style>
   :root { color-scheme: light dark; }
-  body { display: flex; justify-content: center; align-items: center; height: 100vh;}
-  canvas, body {
-    margin: 0;
-    touch-action: none;
-    -webkit-user-select: none;
-    user-select: none;
-  }
-  canvas {
-    width: calc(256px * 3);
-    height: calc(240px * 3);
-    object-fit: contain;
-    image-rendering: pixelated;
-    margin: auto;
-  }
+  body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; touch-action: none; user-select: none; }
+  canvas { width: 768px; height: 768px; object-fit: contain; image-rendering: pixelated; margin: auto; }
 </style>
 <body>
-  <canvas id=c></canvas>
-  <script type="module" src=game.js></script>
-</body>
\ No newline at end of file
+<canvas id=c></canvas>
+<script src=game.js></script>
+</body>
diff --git a/src/js/canvas.js b/src/js/canvas.js
index 71db888..603acf8 100644
--- a/src/js/canvas.js
+++ b/src/js/canvas.js
@@ -1,7 +1,7 @@
 
 // Holds canvas, context and adds endpoints for graphics
 class Canvas {
-    constructor(id="c", w=128, h=128) {
+    constructor(id="c", w=128, h=128, camera=null) {
         this.canvas = document.getElementById(id);
         this.canvas.width = w;
         this.canvas.height = h;
@@ -14,8 +14,7 @@ class Canvas {
         this.height = this.canvas.height;
 
         // camera
-        this.cX = 0;
-        this.cY = 0;
+        this.camera = camera;
     }
 
     setDimensions (w, h) {
@@ -25,50 +24,91 @@ class Canvas {
         this.height = this.canvas.height;
     }
 
-    fill(c="black") {
-        this.ctx.fillStyle = c;
-        this.ctx.fillRect(0, 0, this.width, this.height);
+    fill(color="black", ignoreCamera=false) {
+        this.ctx.fillStyle = color;
+        if (!ignoreCamera) this.cameraStart();
+        this.ctx.fillRect(0, 0, this.width / this.camera.scale, this.height / this.camera.scale);
+        if (!ignoreCamera) this.cameraEnd();
     }
-    
-    drawImage(image, x, y, width = image.width, height = image.height) {
-        this.ctx.drawImage(image, x-this.cX, y-this.cY, width, height);
+
+    drawText(text, x, y, color="#ffffff", size=5, font="monospace") {
+        // this ignores the camera anyway
+        this.ctx.font = `${size}px ${font}`;
+        this.ctx.fillStyle = color;
+        this.ctx.fillText(text, x, y);
     }
 
-    sliceImage(img, x, y, w, h, cropX, cropY, cropW, cropH, direction=0) {
-        // console.debug("sliceImage", img, x, y, w, h, cropX, cropY, cropW, cropH, direction);
-        this.ctx.save();
-        this.ctx.translate((x+w/2)-this.cX, (y+h/2)-this.cY);
-        this.ctx.rotate(direction);
-        this.ctx.drawImage(img, cropX, cropY, cropW, cropH, -w/2, -h/2, w, h);
-        this.ctx.restore();
-        // console.log(`${x}, ${y}, ${w}, ${h}, ${cropX}, ${cropY}, ${cropW}, ${cropH}`);
+    drawImage(img, x, y, w=img.width, h=img.height, ignoreCamera=false) {
+        if (!ignoreCamera && this.isPixelOutOfCamera(x,y)) return;
+        if (!ignoreCamera) this.cameraStart();
+        this.ctx.drawImage(img, x, y, w, h);
+        if (!ignoreCamera) this.cameraEnd();
     }
 
-    drawText(text, x, y, c="white", size=16, font="monospace") {
-        this.ctx.fillStyle = c;
-        this.ctx.font = `${size}px ${font}`;
-        this.ctx.fillText(text, x, y);
+    sliceImage(img, x, y, w, h, sliceX, sliceY, sliceW, sliceH, direction=0, ignoreCamera=true) {
+        if (!ignoreCamera && this.isPixelOutOfCamera(x,y)) return;
+        if (!ignoreCamera) this.cameraStart()
+            else this.ctx.save();
+        if (direction) {
+            this.ctx.translate((w / 2), (h / 2));
+            this.ctx.rotate(direction);
+        }
+        this.ctx.drawImage(img, sliceX, sliceY, sliceW, sliceH, x, y, w, h);
+        // this.ctx.fillRect(x, y, w, h);
+        this.cameraEnd();
     }
 
-    drawLine(x1, y1, x2, y2, c="white", w=1) {
-        this.ctx.strokeStyle = c;
-        this.ctx.lineWidth = w;
+    drawRect(x, y, w, h, color="#ffffff", ignoreCamera=false) {
+        if (!ignoreCamera && this.isPixelOutOfCamera(x,y)) return;
+        this.ctx.fillStyle = color;
+        if (!ignoreCamera) this.cameraStart();
+        this.ctx.fillRect(x, y, w, h);
+        if (!ignoreCamera) this.cameraEnd();
+    }
+
+    drawLine(x1, y1, x2, y2, color="#ffffff", ignoreCamera=false) {
+        if (!ignoreCamera && this.isPixelOutOfCamera(x1,y1)) return;
+        if (!ignoreCamera && this.isPixelOutOfCamera(x2,y2)) return;
+        this.ctx.strokeStyle = color;
+        if (!ignoreCamera) this.cameraStart();
         this.ctx.beginPath();
-        this.ctx.moveTo(x1-this.cX, y1-this.cY);
-        this.ctx.lineTo(x2-this.cX, y2-this.cY);
+        this.ctx.moveTo(x1, y1);
+        this.ctx.lineTo(x2, y2);
         this.ctx.stroke();
+        if (!ignoreCamera) this.cameraEnd();
     }
 
-    drawRect(x, y, w, h, c="white") {
-        this.ctx.fillStyle = c;
-        this.ctx.fillRect(x-this.cX, y-this.cY, w, h);
+    strokeRect(x, y, w, h, color="#ffffff", ignoreCamera=false) {
+        if (!ignoreCamera && this.isPixelOutOfCamera(x,y)) return;
+        this.ctx.strokeStyle = color;
+        if (!ignoreCamera) this.cameraStart();
+        this.ctx.strokeRect(x, y, w, h);
+        if (!ignoreCamera) this.cameraEnd();
     }
 
-    strokeRect(x, y, w, h, c="white") {
-        this.ctx.strokeStyle = c;
-        this.ctx.strokeRect(x-this.cX, y-this.cY, w, h);
-    }
+
+
     
+
+    // The following methods are for code reused when the camera is used
+    cameraStart() {
+        this.ctx.save();
+        this.ctx.setTransform(1,0,0,1,0,0);
+        this.ctx.scale(this.camera.scale, this.camera.scale);
+        this.ctx.translate(-this.camera.x, -this.camera.y);
+        // this.ctx.rotate(this.camera.rotation);
+    }
+
+    isPixelOutOfCamera(x,y) {
+        if (x * this.camera.scale > this.width) return 1;
+        if (y * this.camera.scale > this.height) return 1;
+        return 0;
+    }
+
+    cameraEnd() {
+        this.ctx.restore();
+    }
+
 }
 
 export { Canvas };
diff --git a/src/js/config.js b/src/js/config.js
index 2c6dbab..12a6de7 100644
--- a/src/js/config.js
+++ b/src/js/config.js
@@ -3,4 +3,4 @@
 export const GAME_TITLE = "Deilg–ei";
 
 export const WIDTH = 256; // pixels
-export const HEIGHT = 240; // pixels
+export const HEIGHT = 256; // pixels
diff --git a/src/js/game.js b/src/js/game.js
index 2119149..bbb6b9d 100644
--- a/src/js/game.js
+++ b/src/js/game.js
@@ -2,14 +2,14 @@
 import { WIDTH, HEIGHT, GAME_TITLE } from "./config.js";
 import { Canvas } from "./canvas.js";
 import { TextRenderer } from "./text.js";
-import { Room, GameObject } from "./objects.js";
+import { Room, GameObject, Camera } from "./objects.js";
 import {
     pi,
     getParameter,
     getDirectionBetweenTwoPoints,
     degreesToRadians,
     randomInt,
-    calculateDistanceBetweenTwoPoints, findDuplicateIds
+    calculateDistanceBetweenTwoPoints, findDuplicateIds, pathfind
 } from "./utils.js";
 import { getMousePos } from "./inputs/mouse.js";
 import { isKeyUp, whichKeyDown } from "./inputs/keyboard.js";
@@ -39,7 +39,9 @@ let debug = getParameter("debug") || 0;
 
 let rooms = [];
 let debugStatuses = [];
-let canvas = new Canvas("c", WIDTH, HEIGHT);
+let mainCamera = new Camera();
+mainCamera.scale = 1;
+let canvas = new Canvas("c", WIDTH, HEIGHT, mainCamera);
 let text;
 
 let pressedLastFrame = [];
@@ -92,6 +94,10 @@ let searchForRoom = (name) => {
 }
 
 const changeRoom = (index) => {
+    mainCamera.x = 0;
+    mainCamera.y = 0;
+    mainCamera.scale = 1;
+    mainCamera.rotation = 0;
     currentRoom = rooms[index];
     roomIndex = index;
     currentRoom.init();
@@ -141,8 +147,12 @@ debugRoom.keyDown = (key) => {
     if (debugRoom.index < 0) debugRoom.index = debugRoom.options[debugRoom.submenu].length-1;
 }
 
-debugRoom.draw = () => {
+debugRoom.step = () => {
+    mainCamera.x = Math.sin(currentFrame /(canvas.width / 2)) * canvas.width - 32;
+}
 
+debugRoom.draw = () => {
+    canvas.drawLine(0,0,0,canvas.height);
     canvas.drawRect(Math.sin(currentFrame /(canvas.width / 2)) * canvas.width - 32, canvas.height-64, 32, 32, "#222034");
 }
 debugRoom.drawGUI = () => {
@@ -194,36 +204,42 @@ class Boat extends Entity {
         this.boatsail = randomInt(0,2);
         this.sprite = sprite;
         this.speed = speed;
-        if (speed === 0) speed = 0.5;
-        this.stepspeed = randomInt(Math.floor(500/speed), Math.floor(1500/speed));
+        if (speed === 0) this.speed = 0.5;
+        this.stepspeed = randomInt(Math.floor(500/this.speed), Math.floor(1000/this.speed));
         this.target = target;
+        this.moved = 0;
     }
 
 
     step() {
-        if (!(currentFrame % this.stepspeed)) {
-            this.direction = getDirectionBetweenTwoPoints(this.x, this.y, this.target.x, this.target.y);
+        let { stepspeed, x, y, target, id, moved } = this;
+        if (!(currentFrame % stepspeed)) {
+            this.direction = getDirectionBetweenTwoPoints(x, y, target.x, target.y);
             this.x += 2 * Math.cos(this.direction);
             this.y += 2 * Math.sin(this.direction);
         }
 
-        if (calculateDistanceBetweenTwoPoints(this.x, this.y, this.target.x, this.target.y) < 5) {
-            console.debug(`${this.id} is going bye-bye!`);
-            gameRoom.remove(this.id);
+        const dist = calculateDistanceBetweenTwoPoints(x, y, target.x, target.y);
+        if (dist < 5) {
+            console.debug(`${id} is going bye-bye!`);
+            currentRoom.remove(id);
         }
+        debugStatuses.push(`${id}:${dist}px`)
+
     }
 
     draw() {
-        const displayDirection = this.direction + (pi / 2); // should move PI0.5 clockwise
+        // const displayDirection = this.direction + (pi / 2); // should move PI0.5 clockwise
+        const displayDirection = 0;
         canvas.sliceImage(this.sprite, this.x-2.5, this.y-4, 5, 8, (5*this.boatbody), 0, 5, 8, displayDirection); //body
         canvas.sliceImage(this.sprite, this.x-2.5, this.y-4, 5, 8, (5*this.boatsail), 8, 5, 8, displayDirection); //sail
 
-        if (debug) text.render(`${this.id}`, this.x - 2, this.y - 2);
+        if (debug) canvas.drawLine(this.x, this.y, this.target.x, this.target.y);
     }
 
-    onclick(pos) {
-        this.target = pos;
-    }
+    // onclick(pos) {
+    //     this.target = pos;
+    // }
 }
 
 const endRoom = new Room("end");
@@ -237,6 +253,9 @@ endRoom.wonGame = () => {
 }
 
 const gameRoom = new Room("game");
+gameRoom.init = _ => {
+    mainCamera.scale = 1;
+}
 gameRoom.wave = getParameter("wave");
 if (!gameRoom.wave) gameRoom.wave = 0;
 
@@ -249,8 +268,7 @@ gameRoom.boatCount = 0;
 gameRoom.boatSpeed = 0;
 
 gameRoom.boatAvoid = [
-    {x: 73, y: 102, w: 83, h: 67},
-    {x: 186, y: 42, w: 39, h: 31}
+    {x: 106, y: 84, w: 83, h: 67},
 ];
 
 console.log(typeof(gameRoom.remove))
@@ -259,7 +277,7 @@ gameRoom.startWave = _ => {
     let { wave, boatCount, boatSpeed } = gameRoom;
     gameRoom.wavePendingStart = 0;
     boatCount = Math.floor(1 + (wave * 4));
-    boatSpeed = 3 + (wave * 1.1);
+    boatSpeed = 6 + (wave * 1.1);
 
     for (let i = 0; i <= boatCount; i++) {
         const randomDirection = degreesToRadians(Math.random()*360);
@@ -272,11 +290,11 @@ gameRoom.startWave = _ => {
     }
 }
 
-// gameRoom.init = _ => gameRoom.startWave();
+
 
 gameRoom.background = "#305182"
 gameRoom.draw = () => {
-    canvas.drawImage(assets.images.island, (canvas.width/2 - assets.images.island.width/2)-canvas.cX, (canvas.height/2 - assets.images.island.height/2)-canvas.cY);
+    canvas.drawImage(assets.images.island, (canvas.width/2 - assets.images.island.width/2), (canvas.height/2 - assets.images.island.height/2));
     for (const item of gameRoom.objects) {
         item.draw();
     }
@@ -304,10 +322,78 @@ gameRoom.onclick = (pos) => {
     }
 }
 
+const testRoom = new Room("tiles", 512, 512);
+testRoom.tileSize=8;
+testRoom.tileWidth = testRoom.width / testRoom.tileSize;
+testRoom.tileHeight = testRoom.height / testRoom.tileSize;
+testRoom.currentTile = {x:0,y:0};
+testRoom.lastClickedTile = {x:-10,y:0};
+testRoom.pathfinded = [];
+testRoom.step = _ => {
+    testRoom.currentTile.x = Math.floor(lastMousePos.x/testRoom.tileSize);
+    testRoom.currentTile.y = Math.floor(lastMousePos.y/testRoom.tileSize);
+
+    testRoom.pathfinded = pathfind(testRoom.lastClickedTile.x, testRoom.lastClickedTile.y, testRoom.currentTile.x, testRoom.currentTile.y);
+
+    debugStatuses.push(`Dimension:${testRoom.tileWidth},${testRoom.tileHeight}`);
+    debugStatuses.push(`Camera scale:${mainCamera.scale}`);
+    debugStatuses.push(`Last Clicked Tile:${testRoom.lastClickedTile.x},${testRoom.lastClickedTile.y}`);
+    debugStatuses.push(`Current Tile:${testRoom.currentTile.x},${testRoom.currentTile.y}`);
+    debugStatuses.push(`Difference:${testRoom.currentTile.x-testRoom.lastClickedTile.x},${testRoom.currentTile.y-testRoom.lastClickedTile.y}`)
+    debugStatuses.push(`Pathfind direction:${testRoom.pathfinded}`);
+}
+testRoom.onclick = (pos) => {
+    testRoom.lastClickedTile.x = Math.floor(pos.x/testRoom.tileSize);
+    testRoom.lastClickedTile.y = Math.floor(pos.y/testRoom.tileSize);
+}
+testRoom.draw = _ => {
+    for (let x = 0; x < testRoom.width; x+=testRoom.tileSize) {
+        for (let y = 0; y < testRoom.height; y+=testRoom.tileSize) {
+            canvas.strokeRect(x, y, testRoom.tileSize, testRoom.tileSize, "#222034");
+        }
+    }
+    // draw the tile grid.
+    for (let x = 0; x < testRoom.width; x+=testRoom.tileSize) {
+        for (let y = 0; y < testRoom.height; y+=testRoom.tileSize) {
+            canvas.strokeRect(x, y, testRoom.tileSize, testRoom.tileSize, "#222034");
+        }
+    }
+    canvas.strokeRect(testRoom.lastClickedTile.x*testRoom.tileSize, testRoom.lastClickedTile.y*testRoom.tileSize, testRoom.tileSize, testRoom.tileSize, "red");
+    canvas.strokeRect(testRoom.currentTile.x*testRoom.tileSize, testRoom.currentTile.y*testRoom.tileSize, testRoom.tileSize, testRoom.tileSize, "#b24d0d");
+
+    for (let path of testRoom.pathfinded) {
+        console.log(path);
+        let destinationTile = {x: path.x, y: path.y};
+        switch (path.direction) {
+            case 1:
+                destinationTile.y--;
+                break;
+            case 2:
+                destinationTile.x++;
+                break;
+            case 3:
+                destinationTile.y++;
+                break;
+            case 4:
+                destinationTile.x--;
+                break;
+        }
+        canvas.drawLine((path.x*8)-4, (path.y*8)-4, (destinationTile.x*8)-4, (destinationTile.y*8)-4, "white");
+    }
+}
+testRoom.keyDown = (key) => {
+    if (pressedLastFrame[key]) return;
+    
+    const keyActions = {
+        ArrowUp: () => mainCamera.scale = ((mainCamera.scale*10)+1)/10,
+        ArrowDown: () => mainCamera.scale = ((mainCamera.scale*10)-1)/10,
+    }
 
+    const action = keyActions[key];
+    if (action) action();
+}
 
-
-rooms.push(loadingRoom, menuRoom, debugRoom, gameRoom, endRoom);
+rooms.push(loadingRoom, menuRoom, debugRoom, gameRoom, endRoom, testRoom);
 
 
 currentRoom = rooms[roomIndex];
@@ -315,13 +401,14 @@ currentRoom = rooms[roomIndex];
 let lastMousePos = {x:0,y:0}
 
 const sendMouseToCurrentRoom = evt => {
-    const mousePos = getMousePos(canvas.canvas, evt);
+    const tempMousePos = getMousePos(canvas.canvas, evt);
+    const mousePos = {x:tempMousePos.x/mainCamera.scale,y:tempMousePos.y/mainCamera.scale};
     currentRoom.onclick(mousePos);
 }
 canvas.canvas.addEventListener('mousedown', evt=>sendMouseToCurrentRoom(evt), false);
 canvas.canvas.addEventListener('mousemove', evt => {
     const mousePos = getMousePos(canvas.canvas, evt);
-    lastMousePos={x:mousePos.x,y:mousePos.y}
+    lastMousePos={x:mousePos.x/mainCamera.scale,y:mousePos.y/mainCamera.scale}
 })
 
 
@@ -353,10 +440,16 @@ let main = () => { // main game loop
     pressedLastFrame = currentKeys;
 
     const fps = Math.round(1000 / delta);
-    let fpsColor;
-    (fps > 50) ? fpsColor = "#0d7200" : "#b24d0d";
-    (fps < 40) ? fpsColor = "#8a0000" : "#b24d0d";
-    canvas.drawRect(0,0,20,5, fpsColor);
+    let fpsColor = "#0d7200";
+    if (fps < 30) {
+        fpsColor = "#8a0000";
+        debugStatuses.push("FPS is severly low. Please contact Bye.");
+    } else if (fps <= 45) {
+        fpsColor = "#b24d0d";
+        debugStatuses.push("FPS is low. Please contact Bye.");
+    }
+
+    canvas.drawRect(0,0,20,5, fpsColor, 1);
     text.render(`${fps}/${targetFrames}FPS`, 0, 0);
     if (debug) {
         text.render(currentRoom.name, canvas.width-(text.charWidth*(currentRoom.name.length)), 0);
diff --git a/src/js/inputs/mouse.js b/src/js/inputs/mouse.js
index 6b7c433..1ab92f2 100644
--- a/src/js/inputs/mouse.js
+++ b/src/js/inputs/mouse.js
@@ -1,3 +1,5 @@
+
+
 export const getMousePos = (canvas, evt) => {
     const rect = canvas.getBoundingClientRect();
     let x = evt.clientX - rect.left;
@@ -6,6 +8,8 @@ export const getMousePos = (canvas, evt) => {
     y /= rect.height;
     x *= canvas.width;
     y *= canvas.height;
+    // x /= canvas.camera.scale;
+    // y /= canvas.camera.scale;
     return {x,y};
   }
 
diff --git a/src/js/objects.js b/src/js/objects.js
index 6ccb04a..94cab92 100644
--- a/src/js/objects.js
+++ b/src/js/objects.js
@@ -1,4 +1,9 @@
 class GameObject {
+    constructor(id=0) {
+        this.x = 0;
+        this.y = 0;
+        this.id = 0; // rooms have an id of zero.
+    }
     draw() {}
     step() {}
     keyDown(key) {}
@@ -8,11 +13,13 @@ class GameObject {
 
 
 class Room extends GameObject {
-    constructor(name="") {
+    constructor(name="", w=256, h=240) {
         super();
-        this.objects = [];
-        this.name = name; // needs to be unique, otherwise the searching code will just use the first one it finds.
+        this.objects    = [];
+        this.name       = name; // needs to be unique, otherwise the searching code will just use the first one it finds.
         this.background = "#000000";
+        this.width      = w;
+        this.height     = h;
     }
 
     init(){}
@@ -49,6 +56,13 @@ class Room extends GameObject {
 
 }
 
+class Camera extends GameObject {
+    constructor() {
+        super(); // Cameras are special objects, they don't have an id.
+        this.scale = 1;
+        this.rotation = 0;
+    }
+}
 
 
-export { GameObject, Room };
\ No newline at end of file
+export { GameObject, Room, Camera };
\ No newline at end of file
diff --git a/src/js/text.js b/src/js/text.js
index 0fb6fd2..2ce6762 100644
--- a/src/js/text.js
+++ b/src/js/text.js
@@ -28,7 +28,7 @@ class TextRenderer {
         let yOffset = 0;
         // if the letter is ",", offset it by -1
         if (letter === ",") yOffset = 1;
-        canvas.sliceImage(this.fontimg, x+canvas.cX, y + yOffset + canvas.cY, fontWidth, fontHeight, sx, sy, fontWidth, fontHeight); 
+        canvas.sliceImage(this.fontimg, x, y + yOffset, fontWidth, fontHeight, sx, sy, fontWidth, fontHeight, 0, true);
             // canvas.cX and canvas.cY are the camera offsets. we don't want to have text flying off the screen.
             // you can counteract this by specifying x-cX and x-cY when calling this.
     }
diff --git a/src/js/utils.js b/src/js/utils.js
index e0dce31..8db298a 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -29,9 +29,82 @@ export const getDirectionBetweenTwoPoints = (x1, y1, x2, y2) => {
     return Math.atan2(dY, dX)
 }
 
+export const calculateDirectionBetweenTwoTiles = (start = {x:0, y:0}, end = {x:0, y:0}) => {
+    const dx = end.x - start.x;
+    const dy = end.y - start.y;
+    return Math.sqrt(dx * dx + dy * dy);
+}
 export const calculateDistanceBetweenTwoPoints = (x1, y1, x2, y2) => {
     const dx = x2 - x1;
     const dy = y2 - y1;
     return Math.sqrt(dx * dx + dy * dy);
 }
- 
\ No newline at end of file
+
+const findBestDirection = (start, end) => {
+    const originalStart = { x: start.x, y: start.y }; // Save the original start position
+
+    start.y++;
+    const distTileBelow = calculateDirectionBetweenTwoTiles(start, end);
+    start.x++; start.y--;
+    const distTileRight = calculateDirectionBetweenTwoTiles(start, end);
+    start.x--; start.y--;
+    const distTileAbove = calculateDirectionBetweenTwoTiles(start, end);
+    start.x--; start.y++;
+    const distTileLeft = calculateDirectionBetweenTwoTiles(start, end);
+
+    // Reset the start position to the original
+    start.x = originalStart.x;
+    start.y = originalStart.y;
+
+    const distances = [distTileAbove, distTileRight, distTileBelow, distTileLeft];
+    const smallestDistance = Math.min(...distances);
+    return distances.indexOf(smallestDistance) + 1;
+}
+
+export const pathfind = (x1, y1, x2, y2, reloop = false) => {
+    // Check if currentTile is equal to end
+    if (x1 === x2 && y1 === y2) {
+        return [{ x: x1, y: y1 }];
+    }
+
+    const start = { x: x1, y: y1 };
+    const end = { x: x2, y: y2 };
+    let currentTile = start;
+
+    const foundDirections = [];
+
+    // Loop until currentTile is equal to end
+    while (currentTile.x !== end.x || currentTile.y !== end.y) {
+        const direction = findBestDirection(currentTile, end);
+
+        // Update currentTile based on direction
+        switch (direction) {
+            case 1:
+                currentTile.y--;
+                break;
+            case 2:
+                currentTile.x++;
+                break;
+            case 3:
+                currentTile.y++;
+                break;
+            case 4:
+                currentTile.x--;
+                break;
+        }
+
+        // Add currentTile to foundDirections
+        foundDirections.push({ x: currentTile.x, y: currentTile.y, direction: direction });
+    }
+
+    return foundDirections;
+};
+
+export const levelDatatoCostMap = (levelData=[], avoid=["land"]) => {
+    let costMap = []
+    for (let tile of levelData) {
+        if (avoid.includes(tile.type)) costMap.push({x: tile.x, y: tile.y, cost: 100});
+    }
+    return costMap;
+}
+
-- 
GitLab