summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaksymilian Jopek <maks@jopek.eu>2025-04-28 18:03:28 +0200
committerMaksymilian Jopek <maks@jopek.eu>2025-04-28 18:03:28 +0200
commit0539d6963ebedd554cd3cd36b6c94df9659d6a6b (patch)
tree9408c7928d0725b114d0adfa811ba2bb3859f495
downloadrustberry-frontend-master.tar.gz
rustberry-frontend-master.tar.zst
rustberry-frontend-master.zip
Initial commitHEADmaster
Simple frontend version with mock API
-rw-r--r--.gitignore2
-rw-r--r--deno.lock834
-rw-r--r--index.html15
-rw-r--r--package.json21
-rw-r--r--src/components/Button.module.css29
-rw-r--r--src/components/Button.tsx57
-rw-r--r--src/components/Timer.module.css63
-rw-r--r--src/components/Timer.tsx178
-rw-r--r--src/helpers/api.ts103
-rw-r--r--src/index.css6
-rw-r--r--src/index.tsx17
-rw-r--r--src/pages/*.tsx8
-rw-r--r--src/pages/index.module.css14
-rw-r--r--src/pages/index.tsx33
-rw-r--r--src/vite.env.d.ts2
-rw-r--r--tsconfig.json17
-rw-r--r--vite.config.ts25
17 files changed, 1424 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..76add87
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist \ No newline at end of file
diff --git a/deno.lock b/deno.lock
new file mode 100644
index 0000000..8d7d064
--- /dev/null
+++ b/deno.lock
@@ -0,0 +1,834 @@
+{
+ "version": "4",
+ "specifiers": {
+ "npm:@solidjs/router@~0.15.1": "0.15.3_solid-js@1.9.5__seroval@1.2.1",
+ "npm:solid-js@^1.9.5": "1.9.5_seroval@1.2.1",
+ "npm:vite-plugin-pages@~0.32.4": "0.32.5_vite@6.3.1__picomatch@4.0.2__yaml@2.7.1_yaml@2.7.1",
+ "npm:vite-plugin-solid@^2.11.6": "2.11.6_solid-js@1.9.5__seroval@1.2.1_vite@6.3.1__picomatch@4.0.2__yaml@2.7.1_@babel+core@7.26.10",
+ "npm:vite@6": "6.3.1_picomatch@4.0.2_yaml@2.7.1"
+ },
+ "npm": {
+ "@ampproject/remapping@2.3.0": {
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dependencies": [
+ "@jridgewell/gen-mapping",
+ "@jridgewell/trace-mapping"
+ ]
+ },
+ "@babel/code-frame@7.26.2": {
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dependencies": [
+ "@babel/helper-validator-identifier",
+ "js-tokens",
+ "picocolors"
+ ]
+ },
+ "@babel/compat-data@7.26.8": {
+ "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ=="
+ },
+ "@babel/core@7.26.10": {
+ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
+ "dependencies": [
+ "@ampproject/remapping",
+ "@babel/code-frame",
+ "@babel/generator",
+ "@babel/helper-compilation-targets",
+ "@babel/helper-module-transforms",
+ "@babel/helpers",
+ "@babel/parser",
+ "@babel/template",
+ "@babel/traverse",
+ "@babel/types",
+ "convert-source-map",
+ "debug",
+ "gensync",
+ "json5",
+ "semver"
+ ]
+ },
+ "@babel/generator@7.27.0": {
+ "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
+ "dependencies": [
+ "@babel/parser",
+ "@babel/types",
+ "@jridgewell/gen-mapping",
+ "@jridgewell/trace-mapping",
+ "jsesc"
+ ]
+ },
+ "@babel/helper-compilation-targets@7.27.0": {
+ "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
+ "dependencies": [
+ "@babel/compat-data",
+ "@babel/helper-validator-option",
+ "browserslist",
+ "lru-cache",
+ "semver"
+ ]
+ },
+ "@babel/helper-module-imports@7.18.6": {
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "dependencies": [
+ "@babel/types"
+ ]
+ },
+ "@babel/helper-module-imports@7.25.9": {
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dependencies": [
+ "@babel/traverse",
+ "@babel/types"
+ ]
+ },
+ "@babel/helper-module-transforms@7.26.0_@babel+core@7.26.10": {
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dependencies": [
+ "@babel/core",
+ "@babel/helper-module-imports@7.25.9",
+ "@babel/helper-validator-identifier",
+ "@babel/traverse"
+ ]
+ },
+ "@babel/helper-plugin-utils@7.26.5": {
+ "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="
+ },
+ "@babel/helper-string-parser@7.25.9": {
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="
+ },
+ "@babel/helper-validator-identifier@7.25.9": {
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="
+ },
+ "@babel/helper-validator-option@7.25.9": {
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="
+ },
+ "@babel/helpers@7.27.0": {
+ "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
+ "dependencies": [
+ "@babel/template",
+ "@babel/types"
+ ]
+ },
+ "@babel/parser@7.27.0": {
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "dependencies": [
+ "@babel/types"
+ ]
+ },
+ "@babel/plugin-syntax-jsx@7.25.9_@babel+core@7.26.10": {
+ "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==",
+ "dependencies": [
+ "@babel/core",
+ "@babel/helper-plugin-utils"
+ ]
+ },
+ "@babel/template@7.27.0": {
+ "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+ "dependencies": [
+ "@babel/code-frame",
+ "@babel/parser",
+ "@babel/types"
+ ]
+ },
+ "@babel/traverse@7.27.0": {
+ "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
+ "dependencies": [
+ "@babel/code-frame",
+ "@babel/generator",
+ "@babel/parser",
+ "@babel/template",
+ "@babel/types",
+ "debug",
+ "globals"
+ ]
+ },
+ "@babel/types@7.27.0": {
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "dependencies": [
+ "@babel/helper-string-parser",
+ "@babel/helper-validator-identifier"
+ ]
+ },
+ "@esbuild/aix-ppc64@0.25.2": {
+ "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="
+ },
+ "@esbuild/android-arm64@0.25.2": {
+ "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="
+ },
+ "@esbuild/android-arm@0.25.2": {
+ "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="
+ },
+ "@esbuild/android-x64@0.25.2": {
+ "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="
+ },
+ "@esbuild/darwin-arm64@0.25.2": {
+ "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="
+ },
+ "@esbuild/darwin-x64@0.25.2": {
+ "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="
+ },
+ "@esbuild/freebsd-arm64@0.25.2": {
+ "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="
+ },
+ "@esbuild/freebsd-x64@0.25.2": {
+ "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="
+ },
+ "@esbuild/linux-arm64@0.25.2": {
+ "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="
+ },
+ "@esbuild/linux-arm@0.25.2": {
+ "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="
+ },
+ "@esbuild/linux-ia32@0.25.2": {
+ "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="
+ },
+ "@esbuild/linux-loong64@0.25.2": {
+ "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="
+ },
+ "@esbuild/linux-mips64el@0.25.2": {
+ "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="
+ },
+ "@esbuild/linux-ppc64@0.25.2": {
+ "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="
+ },
+ "@esbuild/linux-riscv64@0.25.2": {
+ "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="
+ },
+ "@esbuild/linux-s390x@0.25.2": {
+ "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="
+ },
+ "@esbuild/linux-x64@0.25.2": {
+ "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="
+ },
+ "@esbuild/netbsd-arm64@0.25.2": {
+ "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="
+ },
+ "@esbuild/netbsd-x64@0.25.2": {
+ "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="
+ },
+ "@esbuild/openbsd-arm64@0.25.2": {
+ "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="
+ },
+ "@esbuild/openbsd-x64@0.25.2": {
+ "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="
+ },
+ "@esbuild/sunos-x64@0.25.2": {
+ "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="
+ },
+ "@esbuild/win32-arm64@0.25.2": {
+ "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="
+ },
+ "@esbuild/win32-ia32@0.25.2": {
+ "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="
+ },
+ "@esbuild/win32-x64@0.25.2": {
+ "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="
+ },
+ "@jridgewell/gen-mapping@0.3.8": {
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dependencies": [
+ "@jridgewell/set-array",
+ "@jridgewell/sourcemap-codec",
+ "@jridgewell/trace-mapping"
+ ]
+ },
+ "@jridgewell/resolve-uri@3.1.2": {
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
+ },
+ "@jridgewell/set-array@1.2.1": {
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
+ },
+ "@jridgewell/sourcemap-codec@1.5.0": {
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ },
+ "@jridgewell/trace-mapping@0.3.25": {
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dependencies": [
+ "@jridgewell/resolve-uri",
+ "@jridgewell/sourcemap-codec"
+ ]
+ },
+ "@nodelib/fs.scandir@2.1.5": {
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dependencies": [
+ "@nodelib/fs.stat",
+ "run-parallel"
+ ]
+ },
+ "@nodelib/fs.stat@2.0.5": {
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
+ },
+ "@nodelib/fs.walk@1.2.8": {
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dependencies": [
+ "@nodelib/fs.scandir",
+ "fastq"
+ ]
+ },
+ "@rollup/rollup-android-arm-eabi@4.40.0": {
+ "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="
+ },
+ "@rollup/rollup-android-arm64@4.40.0": {
+ "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="
+ },
+ "@rollup/rollup-darwin-arm64@4.40.0": {
+ "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ=="
+ },
+ "@rollup/rollup-darwin-x64@4.40.0": {
+ "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA=="
+ },
+ "@rollup/rollup-freebsd-arm64@4.40.0": {
+ "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg=="
+ },
+ "@rollup/rollup-freebsd-x64@4.40.0": {
+ "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw=="
+ },
+ "@rollup/rollup-linux-arm-gnueabihf@4.40.0": {
+ "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA=="
+ },
+ "@rollup/rollup-linux-arm-musleabihf@4.40.0": {
+ "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg=="
+ },
+ "@rollup/rollup-linux-arm64-gnu@4.40.0": {
+ "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg=="
+ },
+ "@rollup/rollup-linux-arm64-musl@4.40.0": {
+ "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ=="
+ },
+ "@rollup/rollup-linux-loongarch64-gnu@4.40.0": {
+ "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg=="
+ },
+ "@rollup/rollup-linux-powerpc64le-gnu@4.40.0": {
+ "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw=="
+ },
+ "@rollup/rollup-linux-riscv64-gnu@4.40.0": {
+ "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA=="
+ },
+ "@rollup/rollup-linux-riscv64-musl@4.40.0": {
+ "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ=="
+ },
+ "@rollup/rollup-linux-s390x-gnu@4.40.0": {
+ "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw=="
+ },
+ "@rollup/rollup-linux-x64-gnu@4.40.0": {
+ "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ=="
+ },
+ "@rollup/rollup-linux-x64-musl@4.40.0": {
+ "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw=="
+ },
+ "@rollup/rollup-win32-arm64-msvc@4.40.0": {
+ "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ=="
+ },
+ "@rollup/rollup-win32-ia32-msvc@4.40.0": {
+ "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA=="
+ },
+ "@rollup/rollup-win32-x64-msvc@4.40.0": {
+ "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ=="
+ },
+ "@solidjs/router@0.15.3_solid-js@1.9.5__seroval@1.2.1": {
+ "integrity": "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==",
+ "dependencies": [
+ "solid-js"
+ ]
+ },
+ "@types/babel__core@7.20.5": {
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dependencies": [
+ "@babel/parser",
+ "@babel/types",
+ "@types/babel__generator",
+ "@types/babel__template",
+ "@types/babel__traverse"
+ ]
+ },
+ "@types/babel__generator@7.27.0": {
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dependencies": [
+ "@babel/types"
+ ]
+ },
+ "@types/babel__template@7.4.4": {
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dependencies": [
+ "@babel/parser",
+ "@babel/types"
+ ]
+ },
+ "@types/babel__traverse@7.20.7": {
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+ "dependencies": [
+ "@babel/types"
+ ]
+ },
+ "@types/debug@4.1.12": {
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dependencies": [
+ "@types/ms"
+ ]
+ },
+ "@types/estree@1.0.7": {
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
+ },
+ "@types/ms@2.1.0": {
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
+ },
+ "acorn@8.14.1": {
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="
+ },
+ "babel-plugin-jsx-dom-expressions@0.39.7_@babel+core@7.26.10": {
+ "integrity": "sha512-8GzVmFla7jaTNWW8W+lTMl9YGva4/06CtwJjySnkYtt8G1v9weCzc2SuF1DfrudcCNb2Doetc1FRg33swBYZCA==",
+ "dependencies": [
+ "@babel/core",
+ "@babel/helper-module-imports@7.18.6",
+ "@babel/plugin-syntax-jsx",
+ "@babel/types",
+ "html-entities",
+ "parse5",
+ "validate-html-nesting"
+ ]
+ },
+ "babel-preset-solid@1.9.5_@babel+core@7.26.10": {
+ "integrity": "sha512-85I3osODJ1LvZbv8wFozROV1vXq32BubqHXAGu73A//TRs3NLI1OFP83AQBUTSQHwgZQmARjHlJciym3we+V+w==",
+ "dependencies": [
+ "@babel/core",
+ "babel-plugin-jsx-dom-expressions"
+ ]
+ },
+ "braces@3.0.3": {
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dependencies": [
+ "fill-range"
+ ]
+ },
+ "browserslist@4.24.4": {
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dependencies": [
+ "caniuse-lite",
+ "electron-to-chromium",
+ "node-releases",
+ "update-browserslist-db"
+ ]
+ },
+ "caniuse-lite@1.0.30001714": {
+ "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg=="
+ },
+ "confbox@0.1.8": {
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="
+ },
+ "confbox@0.2.2": {
+ "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="
+ },
+ "convert-source-map@2.0.0": {
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
+ },
+ "csstype@3.1.3": {
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "debug@4.4.0": {
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dependencies": [
+ "ms"
+ ]
+ },
+ "dequal@2.0.3": {
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
+ },
+ "electron-to-chromium@1.5.137": {
+ "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA=="
+ },
+ "entities@4.5.0": {
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
+ },
+ "esbuild@0.25.2": {
+ "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
+ "dependencies": [
+ "@esbuild/aix-ppc64",
+ "@esbuild/android-arm",
+ "@esbuild/android-arm64",
+ "@esbuild/android-x64",
+ "@esbuild/darwin-arm64",
+ "@esbuild/darwin-x64",
+ "@esbuild/freebsd-arm64",
+ "@esbuild/freebsd-x64",
+ "@esbuild/linux-arm",
+ "@esbuild/linux-arm64",
+ "@esbuild/linux-ia32",
+ "@esbuild/linux-loong64",
+ "@esbuild/linux-mips64el",
+ "@esbuild/linux-ppc64",
+ "@esbuild/linux-riscv64",
+ "@esbuild/linux-s390x",
+ "@esbuild/linux-x64",
+ "@esbuild/netbsd-arm64",
+ "@esbuild/netbsd-x64",
+ "@esbuild/openbsd-arm64",
+ "@esbuild/openbsd-x64",
+ "@esbuild/sunos-x64",
+ "@esbuild/win32-arm64",
+ "@esbuild/win32-ia32",
+ "@esbuild/win32-x64"
+ ]
+ },
+ "escalade@3.2.0": {
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
+ },
+ "esprima-extract-comments@1.1.0": {
+ "integrity": "sha512-sBQUnvJwpeE9QnPrxh7dpI/dp67erYG4WXEAreAMoelPRpMR7NWb4YtwRPn9b+H1uLQKl/qS8WYmyaljTpjIsw==",
+ "dependencies": [
+ "esprima"
+ ]
+ },
+ "esprima@4.0.1": {
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+ },
+ "exsolve@1.0.4": {
+ "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw=="
+ },
+ "extract-comments@1.1.0": {
+ "integrity": "sha512-dzbZV2AdSSVW/4E7Ti5hZdHWbA+Z80RJsJhr5uiL10oyjl/gy7/o+HI1HwK4/WSZhlq4SNKU3oUzXlM13Qx02Q==",
+ "dependencies": [
+ "esprima-extract-comments",
+ "parse-code-context"
+ ]
+ },
+ "fast-glob@3.3.3": {
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dependencies": [
+ "@nodelib/fs.stat",
+ "@nodelib/fs.walk",
+ "glob-parent",
+ "merge2",
+ "micromatch"
+ ]
+ },
+ "fastq@1.19.1": {
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dependencies": [
+ "reusify"
+ ]
+ },
+ "fdir@6.4.3_picomatch@4.0.2": {
+ "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
+ "dependencies": [
+ "picomatch@4.0.2"
+ ]
+ },
+ "fill-range@7.1.1": {
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dependencies": [
+ "to-regex-range"
+ ]
+ },
+ "fsevents@2.3.3": {
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="
+ },
+ "gensync@1.0.0-beta.2": {
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
+ },
+ "glob-parent@5.1.2": {
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": [
+ "is-glob"
+ ]
+ },
+ "globals@11.12.0": {
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+ },
+ "html-entities@2.3.3": {
+ "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="
+ },
+ "is-extglob@2.1.1": {
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
+ },
+ "is-glob@4.0.3": {
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": [
+ "is-extglob"
+ ]
+ },
+ "is-number@7.0.0": {
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+ },
+ "is-what@4.1.16": {
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="
+ },
+ "js-tokens@4.0.0": {
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "jsesc@3.1.0": {
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="
+ },
+ "json5@2.2.3": {
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
+ },
+ "local-pkg@1.1.1": {
+ "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==",
+ "dependencies": [
+ "mlly",
+ "pkg-types@2.1.0",
+ "quansync"
+ ]
+ },
+ "lru-cache@5.1.1": {
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dependencies": [
+ "yallist"
+ ]
+ },
+ "merge-anything@5.1.7": {
+ "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==",
+ "dependencies": [
+ "is-what"
+ ]
+ },
+ "merge2@1.4.1": {
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
+ },
+ "micromatch@4.0.8": {
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dependencies": [
+ "braces",
+ "picomatch@2.3.1"
+ ]
+ },
+ "mlly@1.7.4": {
+ "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+ "dependencies": [
+ "acorn",
+ "pathe",
+ "pkg-types@1.3.1",
+ "ufo"
+ ]
+ },
+ "ms@2.1.3": {
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "nanoid@3.3.11": {
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
+ },
+ "node-releases@2.0.19": {
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
+ },
+ "parse-code-context@1.0.0": {
+ "integrity": "sha512-OZQaqKaQnR21iqhlnPfVisFjBWjhnMl5J9MgbP8xC+EwoVqbXrq78lp+9Zb3ahmLzrIX5Us/qbvBnaS3hkH6OA=="
+ },
+ "parse5@7.2.1": {
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
+ "dependencies": [
+ "entities"
+ ]
+ },
+ "pathe@2.0.3": {
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
+ },
+ "picocolors@1.1.1": {
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "picomatch@2.3.1": {
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
+ },
+ "picomatch@4.0.2": {
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="
+ },
+ "pkg-types@1.3.1": {
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dependencies": [
+ "confbox@0.1.8",
+ "mlly",
+ "pathe"
+ ]
+ },
+ "pkg-types@2.1.0": {
+ "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==",
+ "dependencies": [
+ "confbox@0.2.2",
+ "exsolve",
+ "pathe"
+ ]
+ },
+ "postcss@8.5.3": {
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dependencies": [
+ "nanoid",
+ "picocolors",
+ "source-map-js"
+ ]
+ },
+ "quansync@0.2.10": {
+ "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="
+ },
+ "queue-microtask@1.2.3": {
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
+ },
+ "reusify@1.1.0": {
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
+ },
+ "rollup@4.40.0": {
+ "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
+ "dependencies": [
+ "@rollup/rollup-android-arm-eabi",
+ "@rollup/rollup-android-arm64",
+ "@rollup/rollup-darwin-arm64",
+ "@rollup/rollup-darwin-x64",
+ "@rollup/rollup-freebsd-arm64",
+ "@rollup/rollup-freebsd-x64",
+ "@rollup/rollup-linux-arm-gnueabihf",
+ "@rollup/rollup-linux-arm-musleabihf",
+ "@rollup/rollup-linux-arm64-gnu",
+ "@rollup/rollup-linux-arm64-musl",
+ "@rollup/rollup-linux-loongarch64-gnu",
+ "@rollup/rollup-linux-powerpc64le-gnu",
+ "@rollup/rollup-linux-riscv64-gnu",
+ "@rollup/rollup-linux-riscv64-musl",
+ "@rollup/rollup-linux-s390x-gnu",
+ "@rollup/rollup-linux-x64-gnu",
+ "@rollup/rollup-linux-x64-musl",
+ "@rollup/rollup-win32-arm64-msvc",
+ "@rollup/rollup-win32-ia32-msvc",
+ "@rollup/rollup-win32-x64-msvc",
+ "@types/estree",
+ "fsevents"
+ ]
+ },
+ "run-parallel@1.2.0": {
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dependencies": [
+ "queue-microtask"
+ ]
+ },
+ "semver@6.3.1": {
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+ },
+ "seroval-plugins@1.2.1_seroval@1.2.1": {
+ "integrity": "sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==",
+ "dependencies": [
+ "seroval"
+ ]
+ },
+ "seroval@1.2.1": {
+ "integrity": "sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw=="
+ },
+ "solid-js@1.9.5_seroval@1.2.1": {
+ "integrity": "sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==",
+ "dependencies": [
+ "csstype",
+ "seroval",
+ "seroval-plugins"
+ ]
+ },
+ "solid-refresh@0.6.3_solid-js@1.9.5__seroval@1.2.1": {
+ "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==",
+ "dependencies": [
+ "@babel/generator",
+ "@babel/helper-module-imports@7.25.9",
+ "@babel/types",
+ "solid-js"
+ ]
+ },
+ "source-map-js@1.2.1": {
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+ },
+ "tinyglobby@0.2.12_picomatch@4.0.2": {
+ "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
+ "dependencies": [
+ "fdir",
+ "picomatch@4.0.2"
+ ]
+ },
+ "to-regex-range@5.0.1": {
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dependencies": [
+ "is-number"
+ ]
+ },
+ "ufo@1.6.1": {
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="
+ },
+ "update-browserslist-db@1.1.3_browserslist@4.24.4": {
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dependencies": [
+ "browserslist",
+ "escalade",
+ "picocolors"
+ ]
+ },
+ "validate-html-nesting@1.2.2": {
+ "integrity": "sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg=="
+ },
+ "vite-plugin-pages@0.32.5_vite@6.3.1__picomatch@4.0.2__yaml@2.7.1_yaml@2.7.1": {
+ "integrity": "sha512-GY2JAt+4vZ4BqTtw+4CSUxPgYiqamrMRIzYk2AtJvQHeBoMlctsQW+tgCpKriUKINiKfi6NegbP07r1XrdxTWA==",
+ "dependencies": [
+ "@types/debug",
+ "debug",
+ "dequal",
+ "extract-comments",
+ "fast-glob",
+ "json5",
+ "local-pkg",
+ "picocolors",
+ "vite@6.3.1_yaml@2.7.1_picomatch@4.0.2",
+ "yaml"
+ ]
+ },
+ "vite-plugin-solid@2.11.6_solid-js@1.9.5__seroval@1.2.1_vite@6.3.1__picomatch@4.0.2__yaml@2.7.1_@babel+core@7.26.10": {
+ "integrity": "sha512-Sl5CTqJTGyEeOsmdH6BOgalIZlwH3t4/y0RQuFLMGnvWMBvxb4+lq7x3BSiAw6etf0QexfNJW7HSOO/Qf7pigg==",
+ "dependencies": [
+ "@babel/core",
+ "@types/babel__core",
+ "babel-preset-solid",
+ "merge-anything",
+ "solid-js",
+ "solid-refresh",
+ "vite@6.3.1_yaml@2.7.1_picomatch@4.0.2",
+ "vitefu"
+ ]
+ },
+ "vite@6.3.1_picomatch@4.0.2_yaml@2.7.1": {
+ "integrity": "sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ==",
+ "dependencies": [
+ "esbuild",
+ "fdir",
+ "fsevents",
+ "picomatch@4.0.2",
+ "postcss",
+ "rollup",
+ "tinyglobby",
+ "yaml"
+ ]
+ },
+ "vite@6.3.1_yaml@2.7.1_picomatch@4.0.2": {
+ "integrity": "sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ==",
+ "dependencies": [
+ "esbuild",
+ "fdir",
+ "fsevents",
+ "picomatch@4.0.2",
+ "postcss",
+ "rollup",
+ "tinyglobby",
+ "yaml"
+ ]
+ },
+ "vitefu@1.0.6_vite@6.3.1__yaml@2.7.1__picomatch@4.0.2": {
+ "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==",
+ "dependencies": [
+ "vite@6.3.1_yaml@2.7.1_picomatch@4.0.2"
+ ]
+ },
+ "yallist@3.1.1": {
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ },
+ "yaml@2.7.1": {
+ "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="
+ }
+ },
+ "workspace": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@solidjs/router@~0.15.1",
+ "npm:solid-js@^1.9.5",
+ "npm:vite-plugin-pages@~0.32.4",
+ "npm:vite-plugin-solid@^2.11.6",
+ "npm:vite@6"
+ ]
+ }
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..1905a04
--- /dev/null
+++ b/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="theme-color" content="#000000" />
+ <title>Solid App</title>
+ </head>
+ <body>
+ <noscript>You need to enable JavaScript to run this app.</noscript>
+ <div id="root"></div>
+
+ <script src="/src/index.tsx" type="module"></script>
+ </body>
+</html>
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fbb1949
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "rustberry-frontend",
+ "version": "0.0.0",
+ "description": "",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "license": "AGPL-3.0-or-later",
+ "devDependencies": {
+ "vite": "^6.0.0",
+ "vite-plugin-pages": "^0.32.4",
+ "vite-plugin-solid": "^2.11.6"
+ },
+ "dependencies": {
+ "@solidjs/router": "^0.15.1",
+ "solid-js": "^1.9.5"
+ }
+}
diff --git a/src/components/Button.module.css b/src/components/Button.module.css
new file mode 100644
index 0000000..7041eaf
--- /dev/null
+++ b/src/components/Button.module.css
@@ -0,0 +1,29 @@
+.btn:last-child {
+ margin-bottom: 0;
+}
+.generic-button {
+ display: grid;
+ place-items: center;
+ border: 3px solid #222;
+ color: white;
+ border-radius: 15px;
+ font-size: 1.6em;
+ width: 100%;
+ transition: transform 0.05s;
+ user-select: none;
+}
+.generic-button:enabled:active {
+ transform: scale(0.8);
+ box-shadow: inset 0px 0px 5px 0px black;
+}
+.generic-button:disabled {
+ filter: grayscale(0.8);
+}
+.btn {
+ composes: generic-button;
+ --page-size-without-margins: (
+ 100svh - (var(--rows-count) + 1) * var(--btn-gap)
+ );
+ --btn-height: calc(var(--page-size-without-margins) / var(--rows-count));
+ height: var(--btn-height);
+}
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
new file mode 100644
index 0000000..031fc73
--- /dev/null
+++ b/src/components/Button.tsx
@@ -0,0 +1,57 @@
+import styles from "./Button.module.css";
+import { createEffect, createSignal } from "solid-js";
+
+enum BtnType {
+ click = 0,
+ switch = 1,
+}
+export type Button =
+ | {
+ type: BtnType.click;
+ name: string;
+ color?: string;
+ }
+ | {
+ type: BtnType.switch;
+ name: string;
+ };
+
+export default function Button(props: Button) {
+ const [value, setValue] = createSignal(false);
+ const [bgColor, setBgColor] = createSignal<string>();
+
+ createEffect(() => {
+ if (props.type === BtnType.click) {
+ setBgColor(value() ? "pink" : "blue");
+ } else {
+ setBgColor(value() ? "green" : "red");
+ }
+ });
+
+ function onClick() {
+ if (props.type === BtnType.click) {
+ if (value() === true) {
+ return;
+ }
+ setValue(true);
+ setTimeout(() => {
+ setValue(false);
+ }, 3000);
+ } else {
+ setValue((old) => !old);
+ }
+ }
+
+ return (
+ <button
+ on:click={onClick}
+ class={styles.btn}
+ style={{ "background-color": bgColor() }}
+ >
+ {
+ //btnText()
+ }
+ {props.name}
+ </button>
+ );
+}
diff --git a/src/components/Timer.module.css b/src/components/Timer.module.css
new file mode 100644
index 0000000..b748c9e
--- /dev/null
+++ b/src/components/Timer.module.css
@@ -0,0 +1,63 @@
+.cont {
+ h2 {
+ margin-top: 0.4em;
+ margin-bottom: 1rem;
+ }
+ input {
+ width: 2em;
+ font-size: 1.5em;
+ padding: 0.3em;
+ }
+}
+.custom-option {
+ display: grid;
+ align-items: center;
+ grid-template-columns: 1fr 2fr 1fr;
+ margin-bottom: 1em;
+}
+.units {
+ display: inline-flex;
+ gap: 1.5em;
+ span {
+ font-size: 2em;
+ }
+}
+.not-selected-unit {
+ font-weight: lighter;
+}
+.selected-unit {
+ font-weight: bold;
+ text-decoration: underline;
+}
+.darkgray {
+ color: darkgray;
+}
+.custom-option-btn {
+ composes: generic-button from "./Button.module.css";
+ background-color: green;
+ height: 2em;
+}
+.extra-stop-btn {
+ composes: generic-button from "./Button.module.css";
+ background-color: red;
+ padding: 0.5em;
+ margin-top: 0.5em;
+}
+
+.time-left {
+ margin-bottom: 1em;
+ h2 {
+ display: inline;
+ color: lightblue;
+ }
+}
+
+.options {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+}
+.option {
+ composes: generic-button from "./Button.module.css";
+ height: calc((100vw - 2rem) / 3 / 1.5);
+}
diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx
new file mode 100644
index 0000000..61a98f5
--- /dev/null
+++ b/src/components/Timer.tsx
@@ -0,0 +1,178 @@
+import { createSignal, For, Match, Show, Switch } from "solid-js";
+import styles from "./Timer.module.css";
+
+export const TIME_UNITS = ["s", "m", "h", "d"] as const;
+export type Timer = {
+ title: string;
+ options: {
+ time: number;
+ unit: (typeof TIME_UNITS)[number];
+ }[];
+ backgroundColor: string;
+ onTill: number | null;
+ secondsLeftText: string;
+ extraStopActions: { title: string }[];
+};
+
+export default function Timer(props: Timer) {
+ const [onTill, setOnTill] = createSignal(props.onTill);
+ const [unit, setUnit] = createSignal<(typeof TIME_UNITS)[number]>(
+ TIME_UNITS[1],
+ );
+ const [formattedTime, setFormattedTime] = createSignal("");
+ const [disabled, setDisabled] = createSignal(onTill() !== null);
+ const [customValue, setCustomValue] = createSignal<number | "">("");
+
+ let _intervalId = null;
+ function toggleAutoUpdate() {
+ if (_intervalId === null) {
+ updateTime();
+ _intervalId = setInterval(updateTime, 1000);
+ } else {
+ clearInterval(_intervalId);
+ _intervalId = null;
+ }
+ }
+ if (props.onTill !== null) {
+ toggleAutoUpdate();
+ }
+ function updateTime() {
+ const s = Math.floor(onTill() - Date.now() / 1000);
+ const days = Math.floor(s / (24 * 3600));
+ const hours = Math.floor((s % (24 * 3600)) / 3600);
+ const minutes = Math.floor((s % 3600) / 60);
+ const seconds = s % 60;
+
+ let text = "";
+ if (days > 0) {
+ text += days + "d : ";
+ }
+ if (hours > 0 || text.length > 0) {
+ text += hours + "h : ";
+ }
+ if (minutes > 0 || text.length > 0) {
+ text += minutes + "m : ";
+ }
+ if (seconds > 0 || text.length > 0) {
+ text += seconds + "s";
+ }
+ setFormattedTime(text);
+ }
+ function startStopClick() {
+ const val = customValue();
+ if (disabled() === false && val !== "") {
+ optionClick({ time: val, unit: unit() });
+ } else if (disabled() === true) {
+ setDisabled(false);
+ setOnTill(null);
+ toggleAutoUpdate();
+ }
+ }
+
+ function optionClick(option: Timer["options"][number]) {
+ setDisabled(true);
+ let newOnTill = Math.floor(Date.now() / 1000);
+ if (option.unit === "s") {
+ newOnTill += option.time;
+ } else if (option.unit === "m") {
+ newOnTill += option.time * 60;
+ } else if (option.unit === "h") {
+ newOnTill += option.time * 3600;
+ } else {
+ newOnTill += option.time * 3600 * 24;
+ }
+ setOnTill(newOnTill);
+ toggleAutoUpdate();
+ }
+
+ function extraStopClick(act: Timer["extraStopActions"][number]) {
+ // Some api call for the extra
+ startStopClick();
+ }
+ return (
+ <div class={styles.cont}>
+ <h2>{props.title}</h2>
+
+ <div class={styles.customOption}>
+ <input
+ type="number"
+ value={customValue()}
+ on:input={(e) =>
+ setCustomValue(e.target.value ? parseFloat(e.target.value) : "")
+ }
+ disabled={disabled()}
+ min="1"
+ />
+ <div class={styles.units}>
+ <For each={TIME_UNITS}>
+ {(u) => (
+ <span
+ on:click={() => (disabled() ? null : setUnit(u))}
+ classList={{
+ [styles.selectedUnit]: unit() === u,
+ [styles.notSelectedUnit]: unit() !== u,
+ [styles.darkgray]: disabled() === true,
+ }}
+ >
+ {u}
+ </span>
+ )}
+ </For>
+ </div>
+ <button
+ class={styles.customOptionBtn}
+ on:click={startStopClick}
+ disabled={
+ disabled()
+ ? false
+ : customValue() === "" || (customValue() as number) <= 0
+ }
+ style={{ "background-color": disabled() ? "red" : "green" }}
+ >
+ {disabled() ? "Wyłącz" : "Włącz"}
+ </button>
+ {/*<Show when={disabled() === true}>*/}
+ <For each={props.extraStopActions}>
+ {(act) => (
+ <button
+ disabled={disabled() === false}
+ on:click={[extraStopClick, act]}
+ class={styles.extraStopBtn}
+ >
+ {act.title}
+ </button>
+ )}
+ </For>
+ {/*</Show>*/}
+ </div>
+
+ <div class={styles.timeLeft}>
+ <Switch>
+ <Match when={onTill() === null}>
+ <span>Wyłączone</span>
+ <h2> </h2>
+ </Match>
+ <Match when={onTill() > 0}>
+ <span>{props.secondsLeftText} </span>
+ <h2>{formattedTime()}</h2>
+ </Match>
+ </Switch>
+ </div>
+
+ <div class={styles.options}>
+ <For each={props.options}>
+ {(option) => (
+ <button
+ class={styles.option}
+ on:click={[optionClick, option]}
+ style={{ "background-color": props.backgroundColor }}
+ disabled={disabled()}
+ >
+ {option.time} {option.unit}
+ </button>
+ )}
+ </For>
+ </div>
+ </div>
+ );
+}
diff --git a/src/helpers/api.ts b/src/helpers/api.ts
new file mode 100644
index 0000000..112ba2f
--- /dev/null
+++ b/src/helpers/api.ts
@@ -0,0 +1,103 @@
+import { type Button } from "@/components/Button";
+import { Timer } from "@/components/Timer";
+
+export async function getButtons(): Promise<Button[]> {
+ return [
+ {
+ type: 0,
+ name: "Brama Wjazdowa",
+ },
+ {
+ type: 0,
+ name: "Brama Toyota",
+ },
+ {
+ type: 0,
+ name: "Brama Passat",
+ },
+ {
+ type: 1,
+ name: "Furtka",
+ },
+ {
+ type: 0,
+ name: "Plafon Maks",
+ },
+ {
+ type: 1,
+ name: "Ciepła woda 52,8°C",
+ },
+ {
+ type: 1,
+ name: "Wentylator łazienka",
+ },
+ {
+ type: 1,
+ name: "Wentylator Kuchnia Dolna",
+ },
+ {
+ type: 1,
+ name: "Pompa źródło",
+ },
+ {
+ type: 1,
+ name: "Nawodnienie",
+ },
+ {
+ type: 1,
+ name: "Choinka+Astro",
+ },
+ {
+ type: 1,
+ name: "Komputer",
+ },
+ {
+ type: 1,
+ name: "Oświetlenie Zewnętrzne",
+ },
+ {
+ type: 1,
+ name: "Blok. czujki ośw. zewn.",
+ },
+ {
+ type: 1,
+ name: "Ogrzewanie 27,5°C",
+ },
+ {
+ type: 1,
+ name: "Termometr - restart",
+ },
+ ];
+}
+
+export async function getTimers(): Promise<Timer[]> {
+ return [
+ {
+ title: "Otwórz bramę wjazową na:",
+ options: [
+ { time: 15, unit: "m" },
+ { time: 45, unit: "m" },
+ { time: 1, unit: "h" },
+ { time: 3, unit: "h" },
+ { time: 24, unit: "h" },
+ { time: 48, unit: "h" },
+ ],
+ backgroundColor: "#ab03ab",
+ onTill: Math.floor(Date.now() / 1000) + 3 * 287721,
+ secondsLeftText: "Otwarte jeszcze przez:",
+ extraStopActions: [{ title: "Zamknij i wyłącz" }],
+ },
+ {
+ title: "Włącz ogrzewanie na:",
+ secondsLeftText: "Włączone jeszcze na:",
+ options: [
+ { time: 1, unit: "h" },
+ { time: 1.5, unit: "h" },
+ { time: 2, unit: "h" },
+ { time: 3, unit: "h" },
+ ],
+ backgroundColor: "#ab03ab",
+ onTill: null,
+ },
+ ];
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..f064787
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,6 @@
+body {
+ margin: 0;
+ background-color: black;
+ color: white;
+ padding: 0.3rem;
+}
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..de08c81
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,17 @@
+/* @refresh reload */
+import "./index.css";
+
+import { Suspense } from "solid-js";
+import { render } from "solid-js/web";
+
+import { Router } from "@solidjs/router";
+import routes from "~solid-pages";
+
+render(
+ () => (
+ <Router root={(props) => <Suspense>{props.children}</Suspense>}>
+ {routes}
+ </Router>
+ ),
+ document.getElementById("root"),
+);
diff --git a/src/pages/*.tsx b/src/pages/*.tsx
new file mode 100644
index 0000000..a7021c6
--- /dev/null
+++ b/src/pages/*.tsx
@@ -0,0 +1,8 @@
+export default function NotFound() {
+ return (
+ <section>
+ <h1>404: Not Found</h1>
+ <p>It's gone 😞</p>
+ </section>
+ );
+}
diff --git a/src/pages/index.module.css b/src/pages/index.module.css
new file mode 100644
index 0000000..9d6167d
--- /dev/null
+++ b/src/pages/index.module.css
@@ -0,0 +1,14 @@
+.btns-cont {
+ --btn-gap: 0.3rem;
+ /* padding: var(--btn-gap); */
+ display: grid;
+ justify-items: center;
+ align-items: center;
+ gap: var(--btn-gap);
+ grid-template-columns: repeat(2, 1fr);
+}
+.timers-cont {
+ hr {
+ margin-top: 2em;
+ }
+}
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..d64f9e1
--- /dev/null
+++ b/src/pages/index.tsx
@@ -0,0 +1,33 @@
+import { For } from "solid-js";
+import { createAsync } from "@solidjs/router";
+
+import Button from "@/components/Button";
+import Timer from "@/components/Timer";
+import { getButtons, getTimers } from "@/helpers/api";
+import styles from "./index.module.css";
+
+export default function Home() {
+ const btns = createAsync(getButtons);
+ const timers = createAsync(getTimers);
+
+ return (
+ <>
+ <div
+ class={styles.btnsCont}
+ style={{ "--rows-count": Math.floor(btns()?.length / 2) }}
+ >
+ <For each={btns()}>{(btn) => <Button {...btn} />}</For>
+ </div>
+ <div class={styles.timersCont}>
+ <For each={timers()}>
+ {(timer) => (
+ <>
+ <hr />
+ <Timer {...timer} />
+ </>
+ )}
+ </For>
+ </div>
+ </>
+ );
+}
diff --git a/src/vite.env.d.ts b/src/vite.env.d.ts
new file mode 100644
index 0000000..50d6f23
--- /dev/null
+++ b/src/vite.env.d.ts
@@ -0,0 +1,2 @@
+/// <reference types="vite/client" />
+/// <reference types="vite-plugin-pages/client-solid" />
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..92d678d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["vite/client"],
+ "noEmit": true,
+ "isolatedModules": true,
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ }
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..692c9bf
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,25 @@
+import { defineConfig } from "vite";
+import solidPlugin from "vite-plugin-solid";
+import Pages from "vite-plugin-pages";
+
+export default defineConfig({
+ plugins: [
+ Pages({
+ dirs: ["src/pages"],
+ }),
+ solidPlugin(),
+ ],
+ css: {
+ modules: {
+ localsConvention: "dashesOnly",
+ },
+ },
+ resolve: {
+ alias: {
+ "@": new URL("./src", import.meta.url).pathname,
+ },
+ },
+ build: {
+ target: "esnext",
+ },
+});