diff --git a/astro.config.mjs b/astro.config.mjs index e762ba5..aefc095 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,5 +1,9 @@ // @ts-check import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; + // https://astro.build/config -export default defineConfig({}); +export default defineConfig({ + integrations: [preact()] +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 45b3d5f..12b0c88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,22 @@ "name": "growing-galaxy", "version": "0.0.1", "dependencies": { - "astro": "^5.9.0" + "@astrojs/preact": "^4.1.0", + "astro": "^5.9.0", + "preact": "^10.26.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@astrojs/compiler": { @@ -52,6 +67,24 @@ "vfile": "^6.0.3" } }, + "node_modules/@astrojs/preact": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/preact/-/preact-4.1.0.tgz", + "integrity": "sha512-yXs63ndFHhoKHEZsvYbfsmmZt15QPEziW/twF4uBLAPWjSlZ1Fx/lG+NFMQpGy/CmvI0WkrhyPa9pkJp5ZaVmQ==", + "license": "MIT", + "dependencies": { + "@preact/preset-vite": "^2.10.1", + "@preact/signals": "^2.0.4", + "preact-render-to-string": "^6.5.13", + "vite": "^6.3.5" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "preact": "^10.6.5" + } + }, "node_modules/@astrojs/prism": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", @@ -82,6 +115,169 @@ "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -100,6 +296,28 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { "version": "7.27.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", @@ -115,6 +333,87 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", @@ -910,18 +1209,206 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", "license": "MIT" }, + "node_modules/@preact/preset-vite": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.1.tgz", + "integrity": "sha512-59lyGBXNfZIr5OOuBUB4/IB8AqF/ULbvYnyItgK/2BJnsGJqaeaJobRVtMp1129obHQuj8oZ/dVxB9inmH8Xig==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@prefresh/vite": "^2.4.1", + "@rollup/pluginutils": "^4.1.1", + "babel-plugin-transform-hook-names": "^1.0.2", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "vite-prerender-plugin": "^0.5.3" + }, + "peerDependencies": { + "@babel/core": "7.x", + "vite": "2.x || 3.x || 4.x || 5.x || 6.x" + } + }, + "node_modules/@preact/preset-vite/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@preact/preset-vite/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@preact/preset-vite/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@preact/signals": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-2.2.0.tgz", + "integrity": "sha512-P3KPcEYyVk9Wiwfw68QQzRpPkt0H+zjfH3X4AaGCDlc86GuRBYFGiAxT1nC5F5qlsVIEmjNJ9yVYe7C91z3L+g==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact": ">= 10.25.0" + } + }, + "node_modules/@preact/signals-core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.9.0.tgz", + "integrity": "sha512-uUgFHJLWxb33rfCtb1g+1e3Rg7Jl5EALhGTHlQ5Y0w37OF+fdidYdYEE6crbpUOYDOjlmelIWf0ulXr1ggfUkg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@prefresh/babel-plugin": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.1.tgz", + "integrity": "sha512-uG3jGEAysxWoyG3XkYfjYHgaySFrSsaEb4GagLzYaxlydbuREtaX+FTxuIidp241RaLl85XoHg9Ej6E4+V1pcg==", + "license": "MIT" + }, + "node_modules/@prefresh/core": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.3.tgz", + "integrity": "sha512-nDzxj0tA1/M6APNAWqaxkZ+3sTdPHESa+gol4+Bw7rMc2btWdkLoNH7j9rGhUb8SThC0Vz0VoXtq+U+9azGLHg==", + "license": "MIT", + "peerDependencies": { + "preact": "^10.0.0" + } + }, + "node_modules/@prefresh/utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ==", + "license": "MIT" + }, + "node_modules/@prefresh/vite": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.7.tgz", + "integrity": "sha512-zmCEDWSFHl5A7PciXa/fe+OUjoGi4iiCQclpWfpIg7LjxwWrtlUT4DfxDBcQwHfTyipS/XDm8x7WYrkiTW0q+w==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.22.1", + "@prefresh/babel-plugin": "0.5.1", + "@prefresh/core": "^1.5.1", + "@prefresh/utils": "^1.2.0", + "@rollup/pluginutils": "^4.2.1" + }, + "peerDependencies": { + "preact": "^10.4.0", + "vite": ">=2.0.0" + } + }, + "node_modules/@prefresh/vite/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@prefresh/vite/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@prefresh/vite/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", @@ -1593,6 +2080,15 @@ "node": ">= 0.4" } }, + "node_modules/babel-plugin-transform-hook-names": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz", + "integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==", + "license": "MIT", + "peerDependencies": { + "@babel/core": "^7.12.10" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -1649,6 +2145,12 @@ ], "license": "MIT" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/boxen": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", @@ -1680,6 +2182,38 @@ "base64-js": "^1.1.2" } }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/camelcase": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", @@ -1692,6 +2226,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -1865,6 +2419,12 @@ "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", "license": "ISC" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, "node_modules/cookie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", @@ -1898,6 +2458,22 @@ "uncrypto": "^0.1.3" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/css-tree": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", @@ -1911,6 +2487,18 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2036,6 +2624,73 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dset": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", @@ -2045,6 +2700,12 @@ "node": ">=4" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.165", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", + "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -2109,6 +2770,15 @@ "@esbuild/win32-x64": "0.25.5" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -2212,6 +2882,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-east-asian-width": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", @@ -2230,6 +2909,15 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "license": "ISC" }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/h3": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.3.tgz", @@ -2434,6 +3122,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/html-escaper": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", @@ -2551,6 +3248,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2563,6 +3266,30 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -2572,6 +3299,12 @@ "node": ">=6" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -3493,12 +4226,28 @@ "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", "license": "MIT" }, + "node_modules/node-html-parser": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, "node_modules/node-mock-http": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.0.tgz", "integrity": "sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==", "license": "MIT" }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3508,6 +4257,18 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/ofetch": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", @@ -3673,6 +4434,25 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.26.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.8.tgz", + "integrity": "sha512-1nMfdFjucm5hKvq0IClqZwK4FJkGXhRrQstOQ3P4vp8HxKrJEMFcY6RdBRVTdfQS/UlnX6gfbPuTvaqx/bDoeQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.5.13", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.13.tgz", + "integrity": "sha512-iGPd+hKPMFKsfpR2vL4kJ6ZPcFIoWZEcBf0Dpm3zOpdVvj77aY8RlLiQji5OMrngEyaxGogeakTb54uS2FvA6w==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4073,6 +4853,15 @@ "@types/hast": "^3.0.4" } }, + "node_modules/simple-code-frame": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/simple-code-frame/-/simple-code-frame-1.3.0.tgz", + "integrity": "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==", + "license": "MIT", + "dependencies": { + "kolorist": "^1.6.0" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -4101,6 +4890,15 @@ "url": "https://github.com/sponsors/cyyynthia" } }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4120,6 +4918,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stack-trace": { + "version": "1.0.0-pre2", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", + "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -4560,6 +5367,36 @@ } } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -4676,6 +5513,23 @@ } } }, + "node_modules/vite-prerender-plugin": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/vite-prerender-plugin/-/vite-prerender-plugin-0.5.10.tgz", + "integrity": "sha512-m4i0G5oc3LPLA02uW2XsFZmYNxZdyryz5Ksi78O9puj/ao5c8dBUW06caGwoM1TmYknTBBUyKhtqajUpoP+z8Q==", + "license": "MIT", + "dependencies": { + "kolorist": "^1.8.0", + "magic-string": "0.x >= 0.26.0", + "node-html-parser": "^6.1.12", + "simple-code-frame": "^1.3.0", + "source-map": "^0.7.4", + "stack-trace": "^1.0.0-pre2" + }, + "peerDependencies": { + "vite": "5.x || 6.x" + } + }, "node_modules/vitefu": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", @@ -4767,6 +5621,12 @@ "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", "license": "MIT" }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", diff --git a/package.json b/package.json index 4ad373a..b95e6bc 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "astro": "astro" }, "dependencies": { - "astro": "^5.9.0" + "@astrojs/preact": "^4.1.0", + "astro": "^5.9.0", + "preact": "^10.26.8" } } diff --git a/public/scripts/index.js b/public/scripts/index.js deleted file mode 100644 index dfa9f57..0000000 --- a/public/scripts/index.js +++ /dev/null @@ -1,423 +0,0 @@ -// src/scripts/index.js -// Modularized logic from original index.astro - -// --- State --- -let games = []; -let currentGameId = null; -let playerNameHistory = []; - -// --- DOM Selectors --- -const screens = { - 'new-game-screen': document.getElementById('new-game-screen'), - 'game-list-screen': document.getElementById('game-list-screen'), - 'game-detail-screen': document.getElementById('game-detail-screen'), - 'game-history-screen': document.getElementById('game-history-screen') -}; - -// --- Screen Management --- -function showScreen(screenId) { - const currentScreen = document.querySelector('.screen.active'); - const newScreen = document.getElementById(screenId); - if (currentScreen) { - currentScreen.classList.remove('active'); - currentScreen.classList.add('slide-out'); - setTimeout(() => { - currentScreen.classList.remove('slide-out'); - newScreen.classList.add('active'); - newScreen.classList.add('slide-in'); - setTimeout(() => { - newScreen.classList.remove('slide-in'); - }, 300); - }, 300); - } else { - newScreen.classList.add('active'); - } - if (screenId === 'new-game-screen') { - updateNameHistory(); - } -} - -// --- Loading Overlay --- -function showLoading() { - document.querySelector('.loading-overlay').style.display = 'block'; - document.querySelector('.loading-indicator').style.display = 'block'; -} -function hideLoading() { - document.querySelector('.loading-overlay').style.display = 'none'; - document.querySelector('.loading-indicator').style.display = 'none'; -} - -// --- Game Data Management --- -function loadGames() { - const savedGames = localStorage.getItem('bscscore_games'); - if (savedGames) { - games = JSON.parse(savedGames); - renderGames(); - } -} -function saveGames() { - localStorage.setItem('bscscore_games', JSON.stringify(games)); -} - -// --- Player Name History --- -function updateNameHistory() { - const allNames = games.flatMap(game => [game.player1, game.player2, game.player3].filter(Boolean)); - const nameLastUsed = {}; - games.forEach(game => { - if (game.player1) nameLastUsed[game.player1] = Math.max(nameLastUsed[game.player1] || 0, new Date(game.updatedAt).getTime()); - if (game.player2) nameLastUsed[game.player2] = Math.max(nameLastUsed[game.player2] || 0, new Date(game.updatedAt).getTime()); - if (game.player3) nameLastUsed[game.player3] = Math.max(nameLastUsed[game.player3] || 0, new Date(game.updatedAt).getTime()); - }); - playerNameHistory = [...new Set(Object.keys(nameLastUsed))].sort((a, b) => nameLastUsed[b] - nameLastUsed[a]); - updateNameSelects(); -} -function updateNameSelects() { - const player1Select = document.getElementById('player1-select'); - const player2Select = document.getElementById('player2-select'); - const player3Select = document.getElementById('player3-select'); - while (player1Select.options.length > 1) player1Select.remove(1); - while (player2Select.options.length > 1) player2Select.remove(1); - while (player3Select.options.length > 1) player3Select.remove(1); - playerNameHistory.forEach(name => { - const option1 = new Option(name, name); - const option2 = new Option(name, name); - const option3 = new Option(name, name); - player1Select.add(option1); - player2Select.add(option2); - player3Select.add(option3); - }); -} -function updatePlayerInput(playerId) { - const select = document.getElementById(`${playerId}-select`); - const input = document.getElementById(playerId); - if (select.value) { - input.value = select.value; - } -} - -// --- Validation Modal --- -function showValidationModal(message) { - const modal = document.getElementById('validation-modal'); - const modalMessage = document.getElementById('validation-modal-message'); - modalMessage.textContent = message; - modal.classList.add('show'); -} -function closeValidationModal() { - const modal = document.getElementById('validation-modal'); - modal.classList.remove('show'); -} - -// --- New Game Creation --- -function createNewGame() { - const player1Name = document.getElementById('player1').value.trim(); - const player2Name = document.getElementById('player2').value.trim(); - const player3Name = document.getElementById('player3').value.trim(); - const gameType = document.getElementById('game-type').value; - const raceTo = document.getElementById('race-to').value; - if (!player1Name || !player2Name) { - showValidationModal('Bitte Namen für beide Spieler eingeben'); - return; - } - const newGame = { - id: Date.now(), - player1: player1Name, - player2: player2Name, - player3: player3Name || null, - score1: 0, - score2: 0, - score3: 0, - gameType: gameType, - raceTo: raceTo ? parseInt(raceTo) : null, - status: 'active', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - }; - games.push(newGame); - saveGames(); - updateNameHistory(); - renderGames(); - showGameDetail(newGame.id); -} - -// --- Score Update --- -function updateScore(gameId, player, change) { - const game = games.find(g => g.id === gameId); - if (!game || game.status === 'completed') return; - if (player === 1) { - game.score1 = Math.max(0, game.score1 + change); - } else if (player === 2) { - game.score2 = Math.max(0, game.score2 + change); - } else if (player === 3) { - game.score3 = Math.max(0, game.score3 + change); - } - game.updatedAt = new Date().toISOString(); - if (game.raceTo && (game.score1 >= game.raceTo || game.score2 >= game.raceTo || (game.player3 && game.score3 >= game.raceTo))) { - showGameCompletionModal(); - } - saveGames(); - renderGames(); - if (document.getElementById('game-detail-screen').classList.contains('active')) { - document.getElementById('score1').textContent = game.score1; - document.getElementById('score2').textContent = game.score2; - document.getElementById('score3').textContent = game.score3; - } -} - -// --- Game Deletion --- -function deleteGame(gameId) { - const game = games.find(g => g.id === gameId); - if (!game) return; - currentGameId = gameId; - const modal = document.getElementById('modal'); - const modalTitle = document.getElementById('modal-title'); - const modalMessage = document.getElementById('modal-message'); - modalTitle.textContent = 'Spiel löschen'; - modalMessage.textContent = `Möchten Sie das Spiel zwischen ${game.player1} und ${game.player2} wirklich löschen?`; - modal.classList.add('show'); -} -function closeModal() { - const modal = document.getElementById('modal'); - modal.classList.remove('show'); - currentGameId = null; -} -function confirmDeleteGame(gameId) { - if (!gameId) return; - showLoading(); - setTimeout(() => { - const gameIndex = games.findIndex(g => g.id === gameId); - if (gameIndex !== -1) { - games.splice(gameIndex, 1); - saveGames(); - renderGames(); - if (currentGameId === gameId) { - showScreen('game-list-screen'); - } - } - hideLoading(); - closeModal(); - }, 500); -} - -// --- Game Rendering --- -function renderGames(filter = 'all') { - const gamesContainer = document.getElementById('games-container'); - const filteredGames = games - .filter(game => { - if (filter === 'active') return game.status === 'active'; - if (filter === 'completed') return game.status === 'completed'; - return true; - }) - .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - if (filteredGames.length === 0) { - gamesContainer.innerHTML = '
Keine Spiele vorhanden
'; - return; - } - gamesContainer.innerHTML = filteredGames.map(game => { - const playerNames = game.player3 - ? `${game.player1} vs ${game.player2} vs ${game.player3}` - : `${game.player1} vs ${game.player2}`; - const scores = game.player3 - ? `${game.score1} - ${game.score2} - ${game.score3}` - : `${game.score1} - ${game.score2}`; - return ` -
-
-
${game.gameType}${game.raceTo ? ` | ${game.raceTo}` : ''}
-
${playerNames}
-
${scores}
-
- -
- `; - }).join(''); -} - -// --- Game Detail --- -function showGameDetail(gameId) { - const game = games.find(g => g.id === gameId); - if (!game) return; - currentGameId = gameId; - document.getElementById('game-title').textContent = `${game.gameType}${game.raceTo ? ` | Race to ${game.raceTo}` : ''}`; - document.getElementById('player1-name').textContent = game.player1; - document.getElementById('player2-name').textContent = game.player2; - const player3Score = document.getElementById('player3-score'); - if (game.player3) { - document.getElementById('player3-name').textContent = game.player3; - document.getElementById('score3').textContent = game.score3; - player3Score.style.display = 'flex'; - } else { - player3Score.style.display = 'none'; - } - document.getElementById('score1').textContent = game.score1; - document.getElementById('score2').textContent = game.score2; - const player1Container = document.querySelector('.player-score:first-child'); - const player2Container = document.querySelector('.player-score:nth-child(2)'); - const player3Container = document.getElementById('player3-score'); - player1Container.classList.toggle('franky', game.player1 === 'Fränky'); - player2Container.classList.toggle('franky', game.player2 === 'Fränky'); - player3Container.classList.toggle('franky', game.player3 === 'Fränky'); - const controlButton = document.getElementById('game-control'); - if (game.status === 'completed') { - controlButton.textContent = 'Zurück zur Liste'; - controlButton.onclick = () => showScreen('game-list-screen'); - controlButton.classList.add('disabled'); - } else { - controlButton.textContent = 'Spiel beenden'; - controlButton.onclick = () => finishGame(); - controlButton.classList.remove('disabled'); - } - const scoreButtons = document.querySelectorAll('.score-button'); - scoreButtons.forEach(button => { - button.disabled = game.status === 'completed'; - }); - showScreen('game-detail-screen'); -} - -// --- Game Completion Modal --- -function showGameCompletionModal() { - const game = games.find(g => g.id === currentGameId); - if (!game) return; - const modal = document.getElementById('game-completion-modal'); - const finalScores = modal.querySelector('.final-scores'); - const winnerAnnouncement = modal.querySelector('.winner-announcement'); - let scoreHtml = ''; - scoreHtml += ` -
- ${game.player1} - ${game.score1} -
- `; - scoreHtml += ` -
- ${game.player2} - ${game.score2} -
- `; - if (game.player3) { - scoreHtml += ` -
- ${game.player3} - ${game.score3} -
- `; - } - finalScores.innerHTML = scoreHtml; - const scores = [game.score1, game.score2]; - if (game.player3) scores.push(game.score3); - const maxScore = Math.max(...scores); - const winners = []; - if (game.score1 === maxScore) winners.push(game.player1); - if (game.score2 === maxScore) winners.push(game.player2); - if (game.player3 && game.score3 === maxScore) winners.push(game.player3); - const winnerText = winners.length > 1 - ? `Unentschieden zwischen ${winners.join(' und ')}` - : `${winners[0]} hat gewonnen!`; - winnerAnnouncement.innerHTML = `

${winnerText}

`; - modal.classList.add('show'); -} -function closeGameCompletionModal() { - document.getElementById('game-completion-modal').classList.remove('show'); -} -function confirmGameCompletion() { - const game = games.find(g => g.id === currentGameId); - if (!game) return; - game.status = 'completed'; - game.updatedAt = new Date().toISOString(); - saveGames(); - renderGames(); - showGameDetail(currentGameId); - closeGameCompletionModal(); -} - -// --- Game Finish --- -function finishGame() { - const game = games.find(g => g.id === currentGameId); - if (!game) return; - showGameCompletionModal(); -} - -// --- Filter Games --- -function filterGames(filter) { - document.querySelectorAll('.filter-button').forEach(button => { - button.classList.remove('active'); - }); - document.querySelector(`.filter-button[onclick=\"filterGames('${filter}')\"]`).classList.add('active'); - renderGames(filter); -} - -// --- Fullscreen --- -function toggleFullscreen() { - if (!document.fullscreenElement) { - document.documentElement.requestFullscreen().catch(err => { - console.log(`Error attempting to enable fullscreen: ${err.message}`); - }); - } else { - document.exitFullscreen(); - } -} -document.addEventListener('fullscreenchange', () => { - const button = document.getElementById('fullscreen-toggle'); - if (document.fullscreenElement) { - button.innerHTML = ``; - } else { - button.innerHTML = ``; - } -}); - -// --- Event Listeners --- -document.addEventListener('DOMContentLoaded', () => { - loadGames(); - // Attach event listeners for navigation, game creation, modals, etc. - document.querySelectorAll('.nav-button').forEach(btn => { - if (btn.textContent.includes('Neues Spiel')) btn.onclick = () => showScreen('new-game-screen'); - if (btn.textContent.includes('Abbrechen')) btn.onclick = () => showScreen('game-list-screen'); - if (btn.textContent.includes('Zurück zur Liste')) btn.onclick = () => showScreen('game-list-screen'); - }); - document.querySelectorAll('.filter-button').forEach((btn, idx) => { - if (btn.textContent === 'Alle') btn.onclick = () => filterGames('all'); - if (btn.textContent === 'Aktiv') btn.onclick = () => filterGames('active'); - if (btn.textContent === 'Abgeschlossen') btn.onclick = () => filterGames('completed'); - }); - // Attach fullscreen toggle event - const fullscreenBtn = document.getElementById('fullscreen-toggle'); - if (fullscreenBtn) fullscreenBtn.addEventListener('click', toggleFullscreen); - // Modal buttons - const modal = document.getElementById('modal'); - if (modal) { - modal.querySelector('.close-button').onclick = closeModal; - modal.querySelector('.modal-button.cancel').onclick = closeModal; - modal.querySelector('.modal-button.confirm').onclick = () => confirmDeleteGame(currentGameId); - } - const validationModal = document.getElementById('validation-modal'); - if (validationModal) { - validationModal.querySelector('.close-button').onclick = closeValidationModal; - validationModal.querySelector('.modal-button.cancel').onclick = closeValidationModal; - } - const gameCompletionModal = document.getElementById('game-completion-modal'); - if (gameCompletionModal) { - gameCompletionModal.querySelector('.btn.btn--warning').onclick = confirmGameCompletion; - gameCompletionModal.querySelector('.btn:not(.btn--warning)').onclick = closeGameCompletionModal; - } - // New game creation - const startGameBtn = document.querySelector('.nav-button'); - if (startGameBtn && startGameBtn.textContent.includes('Spiel starten')) { - startGameBtn.onclick = createNewGame; - } - // Player name selects - ['player1', 'player2', 'player3'].forEach(pid => { - const select = document.getElementById(`${pid}-select`); - if (select) select.onchange = () => updatePlayerInput(pid); - }); - // Score buttons - document.querySelectorAll('.score-button').forEach((btn, idx) => { - btn.onclick = () => { - const player = Math.floor(idx / 2) + 1; - const change = idx % 2 === 0 ? -1 : 1; - updateScore(currentGameId, player, change); - }; - }); -}); -// Initial screen -showScreen('game-list-screen'); - -window.toggleFullscreen = toggleFullscreen; \ No newline at end of file diff --git a/src/components/App.jsx b/src/components/App.jsx new file mode 100644 index 0000000..d6d771b --- /dev/null +++ b/src/components/App.jsx @@ -0,0 +1,194 @@ +import { h } from 'preact'; +import { useState, useEffect } from 'preact/hooks'; +import GameList from './GameList.jsx'; +import GameDetail from './GameDetail.jsx'; +import NewGame from './NewGame.jsx'; +import Modal from './Modal.jsx'; +import ValidationModal from './ValidationModal.jsx'; +import GameCompletionModal from './GameCompletionModal.jsx'; +import FullscreenToggle from './FullscreenToggle.jsx'; + +const LOCAL_STORAGE_KEY = 'bscscore_games'; + +export default function App() { + const [games, setGames] = useState([]); + const [currentGameId, setCurrentGameId] = useState(null); + const [playerNameHistory, setPlayerNameHistory] = useState([]); + const [screen, setScreen] = useState('game-list'); + const [loading, setLoading] = useState(false); + const [modal, setModal] = useState({ open: false, gameId: null }); + const [validation, setValidation] = useState({ open: false, message: '' }); + const [completionModal, setCompletionModal] = useState({ open: false, game: null }); + + // Load games from localStorage on mount + useEffect(() => { + const savedGames = localStorage.getItem(LOCAL_STORAGE_KEY); + if (savedGames) { + setGames(JSON.parse(savedGames)); + } + }, []); + + // Save games to localStorage whenever games change + useEffect(() => { + localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(games)); + // Update player name history + const nameLastUsed = {}; + games.forEach(game => { + if (game.player1) nameLastUsed[game.player1] = Math.max(nameLastUsed[game.player1] || 0, new Date(game.updatedAt).getTime()); + if (game.player2) nameLastUsed[game.player2] = Math.max(nameLastUsed[game.player2] || 0, new Date(game.updatedAt).getTime()); + if (game.player3) nameLastUsed[game.player3] = Math.max(nameLastUsed[game.player3] || 0, new Date(game.updatedAt).getTime()); + }); + setPlayerNameHistory( + [...new Set(Object.keys(nameLastUsed))].sort((a, b) => nameLastUsed[b] - nameLastUsed[a]) + ); + }, [games]); + + // Navigation handlers + function showGameList() { + setScreen('game-list'); + setCurrentGameId(null); + } + function showNewGame() { + setScreen('new-game'); + setCurrentGameId(null); + } + function showGameDetail(id) { + setCurrentGameId(id); + setScreen('game-detail'); + } + + // Game creation + function handleCreateGame({ player1, player2, player3, gameType, raceTo }) { + const newGame = { + id: Date.now(), + player1, + player2, + player3, + score1: 0, + score2: 0, + score3: 0, + gameType, + raceTo, + status: 'active', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + setGames(g => [newGame, ...g]); + setScreen('game-list'); + } + + // Score update + function handleUpdateScore(player, change) { + setGames(games => games.map(game => { + if (game.id !== currentGameId || game.status === 'completed') return game; + const updated = { ...game }; + if (player === 1) updated.score1 = Math.max(0, updated.score1 + change); + if (player === 2) updated.score2 = Math.max(0, updated.score2 + change); + if (player === 3) updated.score3 = Math.max(0, updated.score3 + change); + updated.updatedAt = new Date().toISOString(); + // Check for raceTo completion + if (updated.raceTo && (updated.score1 >= updated.raceTo || updated.score2 >= updated.raceTo || (updated.player3 && updated.score3 >= updated.raceTo))) { + setCompletionModal({ open: true, game: updated }); + } + return updated; + })); + } + + // Finish game + function handleFinishGame() { + const game = games.find(g => g.id === currentGameId); + if (!game) return; + setCompletionModal({ open: true, game }); + } + function handleConfirmCompletion() { + setGames(games => games.map(game => { + if (game.id !== currentGameId) return game; + return { ...game, status: 'completed', updatedAt: new Date().toISOString() }; + })); + setCompletionModal({ open: false, game: null }); + setScreen('game-detail'); + } + + // Delete game + function handleDeleteGame(id) { + setModal({ open: true, gameId: id }); + } + function handleConfirmDelete() { + setGames(games => games.filter(g => g.id !== modal.gameId)); + setModal({ open: false, gameId: null }); + setScreen('game-list'); + } + function handleCancelDelete() { + setModal({ open: false, gameId: null }); + } + + // Validation modal + function showValidation(message) { + setValidation({ open: true, message }); + } + function closeValidation() { + setValidation({ open: false, message: '' }); + } + + // Render + return ( + <> +
+ {screen === 'game-list' && ( +
+
+ + +
+
+ )} + {screen === 'new-game' && ( +
+
+ +
+
+ )} + {screen === 'game-detail' && ( +
+
+ g.id === currentGameId)} + onFinishGame={handleFinishGame} + onUpdateScore={handleUpdateScore} + onBack={showGameList} + /> +
+
+ )} + + + setCompletionModal({ open: false, game: null })} + /> +
+ + + ); +} \ No newline at end of file diff --git a/src/components/FullscreenToggle.jsx b/src/components/FullscreenToggle.jsx new file mode 100644 index 0000000..14bbb49 --- /dev/null +++ b/src/components/FullscreenToggle.jsx @@ -0,0 +1,26 @@ +import { h } from 'preact'; +import { useCallback } from 'preact/hooks'; +import styles from './FullscreenToggle.module.css'; + +export default function FullscreenToggle() { + const handleToggle = useCallback(() => { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); + } else { + document.exitFullscreen(); + } + }, []); + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/FullscreenToggle.module.css b/src/components/FullscreenToggle.module.css new file mode 100644 index 0000000..5d98b67 --- /dev/null +++ b/src/components/FullscreenToggle.module.css @@ -0,0 +1,37 @@ +.fullscreenToggle { + position: fixed; + bottom: 20px; + right: 20px; + width: 48px; + height: 48px; + border-radius: 50%; + background-color: rgba(52, 152, 219, 0.9); + border: none; + color: white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + z-index: 9999; + transition: background-color 0.2s, transform 0.2s; +} +.fullscreenToggle:hover { + background-color: rgba(52, 152, 219, 1); + transform: scale(1.1); +} +.fullscreenToggle:active { + transform: scale(0.95); +} +.fullscreenToggle svg { + width: 24px; + height: 24px; +} +@media screen and (max-width: 480px) { + .fullscreenToggle { + bottom: 15px; + right: 15px; + width: 40px; + height: 40px; + } +} \ No newline at end of file diff --git a/src/components/GameCompletionModal.astro b/src/components/GameCompletionModal.astro deleted file mode 100644 index 6d6d318..0000000 --- a/src/components/GameCompletionModal.astro +++ /dev/null @@ -1,16 +0,0 @@ ---- ---- - - \ No newline at end of file diff --git a/src/components/GameCompletionModal.jsx b/src/components/GameCompletionModal.jsx new file mode 100644 index 0000000..25f8f5a --- /dev/null +++ b/src/components/GameCompletionModal.jsx @@ -0,0 +1,38 @@ +import { h } from 'preact'; +import styles from './GameCompletionModal.module.css'; + +export default function GameCompletionModal({ open, game, onConfirm, onClose }) { + if (!open || !game) return null; + const playerNames = [game.player1, game.player2, game.player3].filter(Boolean); + const scores = [game.score1, game.score2, game.score3].filter((_, i) => playerNames[i]); + const maxScore = Math.max(...scores); + const winners = playerNames.filter((name, idx) => scores[idx] === maxScore); + const winnerText = winners.length > 1 + ? `Unentschieden zwischen ${winners.join(' und ')}` + : `${winners[0]} hat gewonnen!`; + return ( +
+
+
+ Spiel beendet + +
+
+
+ {playerNames.map((name, idx) => ( +
+ {name} + {scores[idx]} +
+ ))} +
+

{winnerText}

+
+
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/GameCompletionModal.module.css b/src/components/GameCompletionModal.module.css new file mode 100644 index 0000000..f85df81 --- /dev/null +++ b/src/components/GameCompletionModal.module.css @@ -0,0 +1,85 @@ +#game-completion-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + z-index: 1000; + display: none; + justify-content: center; + align-items: center; +} +#game-completion-modal.show { + display: flex; +} +#game-completion-modal .modal-content { + background-color: #2a2a2a; + padding: 30px; + border-radius: 10px; + width: 90%; + max-width: 500px; + position: relative; + margin: auto; + transform: translateY(0); +} +.final-scores { + margin: 20px 0; +} +.final-score { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + margin-bottom: 10px; + background: #333; + border-radius: 5px; + font-size: 24px; +} +.final-score .player-name { + font-size: 24px; + font-weight: bold; + color: white; +} +.final-score .score { + font-size: 24px; + font-weight: bold; + color: white; +} +.winner-announcement { + text-align: center; + margin: 20px 0; + padding: 20px; + background: #4CAF50; + border-radius: 5px; +} +.winner-announcement h3 { + font-size: 24px; + margin: 0; + color: white; +} +.modal-buttons { + display: flex; + gap: 10px; + margin-top: 20px; +} +.modal-buttons .btn { + flex: 1; + padding: 20px; + font-size: 20px; + border: none; + border-radius: 0; + cursor: pointer; + color: white; +} +.modal-buttons .btn--warning { + background: #f44336; +} +.modal-buttons .btn:not(.btn--warning) { + background: #333; +} +#game-completion-modal h2 { + font-size: 24px; + margin-bottom: 20px; + color: white; +} \ No newline at end of file diff --git a/src/components/GameDetail.astro b/src/components/GameDetail.astro deleted file mode 100644 index 5762d58..0000000 --- a/src/components/GameDetail.astro +++ /dev/null @@ -1,41 +0,0 @@ ---- ---- -
-
-
8-Ball | Race to 5
-
- -
-
-
-
Spieler 1
-
0
-
- - -
-
-
-
Spieler 2
-
0
-
- - -
-
- -
-
- - -
-
-
- \ No newline at end of file diff --git a/src/components/GameDetail.jsx b/src/components/GameDetail.jsx new file mode 100644 index 0000000..feca09e --- /dev/null +++ b/src/components/GameDetail.jsx @@ -0,0 +1,31 @@ +import { h } from 'preact'; +import styles from './GameDetail.module.css'; + +export default function GameDetail({ game, onFinishGame, onUpdateScore, onBack }) { + if (!game) return null; + const isCompleted = game.status === 'completed'; + const playerNames = [game.player1, game.player2, game.player3].filter(Boolean); + const scores = [game.score1, game.score2, game.score3].filter((_, i) => playerNames[i]); + + return ( +
+
{game.gameType}{game.raceTo ? ` | Race to ${game.raceTo}` : ''}
+
+ {playerNames.map((name, idx) => ( +
+ {name} + {scores[idx]} + + +
+ ))} +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/GameDetail.module.css b/src/components/GameDetail.module.css new file mode 100644 index 0000000..3ddae15 --- /dev/null +++ b/src/components/GameDetail.module.css @@ -0,0 +1,103 @@ +.screen { + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 100vh; + display: none; + opacity: 0; + transform: translateX(100%); + transition: transform 0.3s ease, opacity 0.3s ease; +} +.screen.active { + display: block; + opacity: 1; + transform: translateX(0); + position: relative; +} +.screen-content { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: 20px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +.game-title { + font-size: 24px; + color: #ccc; +} +.game-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + width: 100%; +} +.scores-container { + display: flex; + justify-content: space-between; + gap: 20px; + min-height: 0; +} +.player-score { + flex: 1; + text-align: center; + padding: 20px; + background-color: #333; + border-radius: 10px; + display: flex; + flex-direction: column; + min-height: 0; +} +.player-name { + font-size: 24px; + margin-bottom: 10px; + color: #fff; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.score { + font-size: 50vh; + font-weight: bold; + margin: 10px 0; + line-height: 1; +} +.score-buttons { + display: flex; + justify-content: center; + gap: 10px; + margin-top: auto; +} +.score-button { + background-color: #333; + color: white; + border: none; + border-radius: 5px; + padding: 10px 20px; + font-size: 18px; + cursor: pointer; + transition: background-color 0.2s; + min-width: 80px; +} +.game-controls { + display: flex; + gap: 10px; + margin-top: 20px; + width: 100%; +} +.control-button { + flex: 1; + padding: 30px; + background: #333; + color: white; + border: none; + border-radius: 0; + font-size: 24px; + cursor: pointer; + touch-action: manipulation; +} +.control-button.delete { + background: #f44336; +} \ No newline at end of file diff --git a/src/components/GameHistory.astro b/src/components/GameHistory.astro deleted file mode 100644 index beff719..0000000 --- a/src/components/GameHistory.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- ---- -
-
-

Spielhistorie

- -
-
- \ No newline at end of file diff --git a/src/components/GameHistory.module.css b/src/components/GameHistory.module.css new file mode 100644 index 0000000..fc67a38 --- /dev/null +++ b/src/components/GameHistory.module.css @@ -0,0 +1,40 @@ +.screen { + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 100vh; + display: none; + opacity: 0; + transform: translateX(100%); + transition: transform 0.3s ease, opacity 0.3s ease; +} +.screen-content { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: 20px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +.screen-title { + font-size: 24px; + margin-bottom: 20px; +} +.nav-buttons { + display: flex; + flex-direction: column; + gap: 10px; + margin: 20px 0 40px 0; +} +.btn { + flex: 1; + min-width: 100px; + padding: 20px; + color: white; + border: none; + border-radius: 0; + font-size: 20px; + cursor: pointer; + touch-action: manipulation; +} \ No newline at end of file diff --git a/src/components/GameList.astro b/src/components/GameList.astro deleted file mode 100644 index 3656cb5..0000000 --- a/src/components/GameList.astro +++ /dev/null @@ -1,21 +0,0 @@ ---- ---- -
-
-

Spiele

-
- -
- - - -
-
- -
-
-
-
- \ No newline at end of file diff --git a/src/components/GameList.jsx b/src/components/GameList.jsx new file mode 100644 index 0000000..9dad5f8 --- /dev/null +++ b/src/components/GameList.jsx @@ -0,0 +1,41 @@ +import { h } from 'preact'; +import styles from './GameList.module.css'; + +export default function GameList({ games, filter = 'all', onShowGameDetail, onDeleteGame }) { + const filteredGames = games + .filter(game => { + if (filter === 'active') return game.status === 'active'; + if (filter === 'completed') return game.status === 'completed'; + return true; + }) + .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + + if (filteredGames.length === 0) { + return
Keine Spiele vorhanden
; + } + + return ( +
+ {filteredGames.map(game => { + const playerNames = game.player3 + ? `${game.player1} vs ${game.player2} vs ${game.player3}` + : `${game.player1} vs ${game.player2}`; + const scores = game.player3 + ? `${game.score1} - ${game.score2} - ${game.score3}` + : `${game.score1} - ${game.score2}`; + return ( +
+
onShowGameDetail(game.id)}> +
{game.gameType}{game.raceTo ? ` | ${game.raceTo}` : ''}
+
{playerNames}
+
{scores}
+
+ +
+ ); + })} +
+ ); +} \ No newline at end of file diff --git a/src/components/GameList.module.css b/src/components/GameList.module.css new file mode 100644 index 0000000..d0e20e1 --- /dev/null +++ b/src/components/GameList.module.css @@ -0,0 +1,46 @@ +.screen.active { + display: block; + opacity: 1; + transform: translateX(0); + position: relative; +} +.screen-content { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: 20px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +.screen-title { + font-size: 24px; + margin-bottom: 20px; +} +.game-list { + width: 100%; + flex: 1; + overflow-y: auto; +} +.nav-buttons { + display: flex; + flex-direction: column; + gap: 10px; + margin: 20px 0 40px 0; +} +.btn { + flex: 1; + min-width: 100px; + padding: 20px; + color: white; + border: none; + border-radius: 0; + font-size: 20px; + cursor: pointer; + touch-action: manipulation; +} +.filter-button { + background: #333; +} +.filter-button.active { + background: #4CAF50; +} \ No newline at end of file diff --git a/src/components/Modal.astro b/src/components/Modal.astro deleted file mode 100644 index 043b3ca..0000000 --- a/src/components/Modal.astro +++ /dev/null @@ -1,18 +0,0 @@ ---- ---- - - \ No newline at end of file diff --git a/src/components/Modal.jsx b/src/components/Modal.jsx new file mode 100644 index 0000000..bc9314d --- /dev/null +++ b/src/components/Modal.jsx @@ -0,0 +1,23 @@ +import { h } from 'preact'; +import styles from './Modal.module.css'; + +export default function Modal({ open, title, message, onCancel, onConfirm }) { + if (!open) return null; + return ( +
+
+
+ {title} + +
+
+
{message}
+
+
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Modal.module.css b/src/components/Modal.module.css new file mode 100644 index 0000000..6ac0b2b --- /dev/null +++ b/src/components/Modal.module.css @@ -0,0 +1,63 @@ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + z-index: 1000; +} +.modal.show { + display: flex; + justify-content: center; + align-items: center; +} +.modal-content { + background-color: #2a2a2a; + padding: 20px; + border-radius: 10px; + width: 90%; + max-width: 500px; + position: relative; +} +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.close-button { + font-size: 24px; + cursor: pointer; + color: #888; +} +.close-button:hover { + color: white; +} +.modal-body { + margin-bottom: 20px; +} +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; +} +.modal-button { + padding: 8px 16px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} +.modal-button.cancel { + background-color: #444; + color: white; +} +.modal-button.confirm { + background-color: #e74c3c; + color: white; +} +.modal-button:hover { + opacity: 0.9; +} \ No newline at end of file diff --git a/src/components/NewGame.astro b/src/components/NewGame.astro deleted file mode 100644 index beb42c2..0000000 --- a/src/components/NewGame.astro +++ /dev/null @@ -1,50 +0,0 @@ ---- ---- -
-
-

Neues Spiel

-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
-
-
-
- - -
-
- - -
-
- -
-
- \ No newline at end of file diff --git a/src/components/NewGame.jsx b/src/components/NewGame.jsx new file mode 100644 index 0000000..8e4b997 --- /dev/null +++ b/src/components/NewGame.jsx @@ -0,0 +1,71 @@ +import { h } from 'preact'; +import { useState } from 'preact/hooks'; +import styles from './NewGame.module.css'; + +export default function NewGame({ onCreateGame, playerNameHistory, onCancel }) { + const [player1, setPlayer1] = useState(''); + const [player2, setPlayer2] = useState(''); + const [player3, setPlayer3] = useState(''); + const [gameType, setGameType] = useState('8-Ball'); + const [raceTo, setRaceTo] = useState(''); + const [error, setError] = useState(null); + + function handleSubmit(e) { + e.preventDefault(); + if (!player1.trim() || !player2.trim()) { + setError('Bitte Namen für beide Spieler eingeben'); + return; + } + onCreateGame({ + player1: player1.trim(), + player2: player2.trim(), + player3: player3.trim() || null, + gameType, + raceTo: raceTo ? parseInt(raceTo) : null + }); + setPlayer1(''); setPlayer2(''); setPlayer3(''); setGameType('8-Ball'); setRaceTo(''); setError(null); + } + + return ( +
+
+ + setPlayer1(e.target.value)} list="player1-history" /> + + {playerNameHistory.map(name => +
+
+ + setPlayer2(e.target.value)} list="player2-history" /> + + {playerNameHistory.map(name => +
+
+ + setPlayer3(e.target.value)} list="player3-history" /> + + {playerNameHistory.map(name => +
+
+ + +
+
+ + setRaceTo(e.target.value)} min="1" /> +
+ {error &&
{error}
} +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/NewGame.module.css b/src/components/NewGame.module.css new file mode 100644 index 0000000..87aa687 --- /dev/null +++ b/src/components/NewGame.module.css @@ -0,0 +1,111 @@ +.screen { + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 100vh; + display: none; + opacity: 0; + transform: translateX(100%); + transition: transform 0.3s ease, opacity 0.3s ease; +} +.screen-content { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: 20px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +.screen-title { + font-size: 24px; + margin-bottom: 20px; +} +.player-inputs { + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + flex: 1; +} +.player-input { + margin-bottom: 20px; +} +.player-input label { + display: block; + margin-bottom: 10px; + color: #ccc; + font-size: 24px; +} +.name-input-container { + display: flex; + gap: 10px; +} +.name-select { + flex: 1; + padding: 30px; + border: 2px solid #333; + background: #2a2a2a; + color: white; + font-size: 24px; + min-height: 44px; +} +.name-input { + flex: 2; + padding: 30px; + border: 2px solid #333; + background: #2a2a2a; + color: white; + font-size: 24px; + min-height: 44px; +} +.name-select:focus, .name-input:focus { + outline: none; + border-color: #666; +} +.game-settings { + margin-top: 20px; + width: 100%; +} +.setting-group { + margin-bottom: 20px; +} +.setting-group select { + width: 100%; + padding: 30px; + border: 2px solid #333; + background: #2a2a2a; + color: white; + font-size: 24px; + min-height: 44px; + padding: 12px; +} +.setting-group input { + width: 100%; + padding: 30px; + border: 2px solid #333; + background: #2a2a2a; + color: white; + font-size: 24px; +} +.setting-group input:focus { + outline: none; + border-color: #666; +} +.nav-buttons { + display: flex; + flex-direction: column; + gap: 10px; + margin: 20px 0 40px 0; +} +.btn { + flex: 1; + min-width: 100px; + padding: 20px; + color: white; + border: none; + border-radius: 0; + font-size: 20px; + cursor: pointer; + touch-action: manipulation; +} \ No newline at end of file diff --git a/src/components/ValidationModal.astro b/src/components/ValidationModal.astro deleted file mode 100644 index ea59dea..0000000 --- a/src/components/ValidationModal.astro +++ /dev/null @@ -1,17 +0,0 @@ ---- ---- - - \ No newline at end of file diff --git a/src/components/ValidationModal.jsx b/src/components/ValidationModal.jsx new file mode 100644 index 0000000..f181790 --- /dev/null +++ b/src/components/ValidationModal.jsx @@ -0,0 +1,22 @@ +import { h } from 'preact'; +import styles from './ValidationModal.module.css'; + +export default function ValidationModal({ open, message, onClose }) { + if (!open) return null; + return ( +
+
+
+ Fehler + +
+
+
{message}
+
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/ValidationModal.module.css b/src/components/ValidationModal.module.css new file mode 100644 index 0000000..6ac0b2b --- /dev/null +++ b/src/components/ValidationModal.module.css @@ -0,0 +1,63 @@ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + z-index: 1000; +} +.modal.show { + display: flex; + justify-content: center; + align-items: center; +} +.modal-content { + background-color: #2a2a2a; + padding: 20px; + border-radius: 10px; + width: 90%; + max-width: 500px; + position: relative; +} +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.close-button { + font-size: 24px; + cursor: pointer; + color: #888; +} +.close-button:hover { + color: white; +} +.modal-body { + margin-bottom: 20px; +} +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; +} +.modal-button { + padding: 8px 16px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} +.modal-button.cancel { + background-color: #444; + color: white; +} +.modal-button.confirm { + background-color: #e74c3c; + color: white; +} +.modal-button:hover { + opacity: 0.9; +} \ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index 85f0446..92cfc66 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,12 +1,6 @@ --- -import NewGame from "../components/NewGame.astro"; -import GameList from "../components/GameList.astro"; -import GameDetail from "../components/GameDetail.astro"; -import GameHistory from "../components/GameHistory.astro"; -import Modal from "../components/Modal.astro"; -import ValidationModal from "../components/ValidationModal.astro"; -import GameCompletionModal from "../components/GameCompletionModal.astro"; import "../styles/index.css"; +import App from "../components/App.jsx"; --- @@ -25,19 +19,7 @@ import "../styles/index.css";
- - - - +
- - - - - \ No newline at end of file diff --git a/src/scripts/index.js b/src/scripts/index.js deleted file mode 100644 index 9bfcf05..0000000 --- a/src/scripts/index.js +++ /dev/null @@ -1,421 +0,0 @@ -// src/scripts/index.js -// Modularized logic from original index.astro - -// --- State --- -let games = []; -let currentGameId = null; -let playerNameHistory = []; - -// --- DOM Selectors --- -const screens = { - 'new-game-screen': document.getElementById('new-game-screen'), - 'game-list-screen': document.getElementById('game-list-screen'), - 'game-detail-screen': document.getElementById('game-detail-screen'), - 'game-history-screen': document.getElementById('game-history-screen') -}; - -// --- Screen Management --- -function showScreen(screenId) { - const currentScreen = document.querySelector('.screen.active'); - const newScreen = document.getElementById(screenId); - if (currentScreen) { - currentScreen.classList.remove('active'); - currentScreen.classList.add('slide-out'); - setTimeout(() => { - currentScreen.classList.remove('slide-out'); - newScreen.classList.add('active'); - newScreen.classList.add('slide-in'); - setTimeout(() => { - newScreen.classList.remove('slide-in'); - }, 300); - }, 300); - } else { - newScreen.classList.add('active'); - } - if (screenId === 'new-game-screen') { - updateNameHistory(); - } -} - -// --- Loading Overlay --- -function showLoading() { - document.querySelector('.loading-overlay').style.display = 'block'; - document.querySelector('.loading-indicator').style.display = 'block'; -} -function hideLoading() { - document.querySelector('.loading-overlay').style.display = 'none'; - document.querySelector('.loading-indicator').style.display = 'none'; -} - -// --- Game Data Management --- -function loadGames() { - const savedGames = localStorage.getItem('bscscore_games'); - if (savedGames) { - games = JSON.parse(savedGames); - renderGames(); - } -} -function saveGames() { - localStorage.setItem('bscscore_games', JSON.stringify(games)); -} - -// --- Player Name History --- -function updateNameHistory() { - const allNames = games.flatMap(game => [game.player1, game.player2, game.player3].filter(Boolean)); - const nameLastUsed = {}; - games.forEach(game => { - if (game.player1) nameLastUsed[game.player1] = Math.max(nameLastUsed[game.player1] || 0, new Date(game.updatedAt).getTime()); - if (game.player2) nameLastUsed[game.player2] = Math.max(nameLastUsed[game.player2] || 0, new Date(game.updatedAt).getTime()); - if (game.player3) nameLastUsed[game.player3] = Math.max(nameLastUsed[game.player3] || 0, new Date(game.updatedAt).getTime()); - }); - playerNameHistory = [...new Set(Object.keys(nameLastUsed))].sort((a, b) => nameLastUsed[b] - nameLastUsed[a]); - updateNameSelects(); -} -function updateNameSelects() { - const player1Select = document.getElementById('player1-select'); - const player2Select = document.getElementById('player2-select'); - const player3Select = document.getElementById('player3-select'); - while (player1Select.options.length > 1) player1Select.remove(1); - while (player2Select.options.length > 1) player2Select.remove(1); - while (player3Select.options.length > 1) player3Select.remove(1); - playerNameHistory.forEach(name => { - const option1 = new Option(name, name); - const option2 = new Option(name, name); - const option3 = new Option(name, name); - player1Select.add(option1); - player2Select.add(option2); - player3Select.add(option3); - }); -} -function updatePlayerInput(playerId) { - const select = document.getElementById(`${playerId}-select`); - const input = document.getElementById(playerId); - if (select.value) { - input.value = select.value; - } -} - -// --- Validation Modal --- -function showValidationModal(message) { - const modal = document.getElementById('validation-modal'); - const modalMessage = document.getElementById('validation-modal-message'); - modalMessage.textContent = message; - modal.classList.add('show'); -} -function closeValidationModal() { - const modal = document.getElementById('validation-modal'); - modal.classList.remove('show'); -} - -// --- New Game Creation --- -function createNewGame() { - const player1Name = document.getElementById('player1').value.trim(); - const player2Name = document.getElementById('player2').value.trim(); - const player3Name = document.getElementById('player3').value.trim(); - const gameType = document.getElementById('game-type').value; - const raceTo = document.getElementById('race-to').value; - if (!player1Name || !player2Name) { - showValidationModal('Bitte Namen für beide Spieler eingeben'); - return; - } - const newGame = { - id: Date.now(), - player1: player1Name, - player2: player2Name, - player3: player3Name || null, - score1: 0, - score2: 0, - score3: 0, - gameType: gameType, - raceTo: raceTo ? parseInt(raceTo) : null, - status: 'active', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - }; - games.push(newGame); - saveGames(); - updateNameHistory(); - renderGames(); - showGameDetail(newGame.id); -} - -// --- Score Update --- -function updateScore(gameId, player, change) { - const game = games.find(g => g.id === gameId); - if (!game || game.status === 'completed') return; - if (player === 1) { - game.score1 = Math.max(0, game.score1 + change); - } else if (player === 2) { - game.score2 = Math.max(0, game.score2 + change); - } else if (player === 3) { - game.score3 = Math.max(0, game.score3 + change); - } - game.updatedAt = new Date().toISOString(); - if (game.raceTo && (game.score1 >= game.raceTo || game.score2 >= game.raceTo || (game.player3 && game.score3 >= game.raceTo))) { - showGameCompletionModal(); - } - saveGames(); - renderGames(); - if (document.getElementById('game-detail-screen').classList.contains('active')) { - document.getElementById('score1').textContent = game.score1; - document.getElementById('score2').textContent = game.score2; - document.getElementById('score3').textContent = game.score3; - } -} - -// --- Game Deletion --- -function deleteGame(gameId) { - const game = games.find(g => g.id === gameId); - if (!game) return; - currentGameId = gameId; - const modal = document.getElementById('modal'); - const modalTitle = document.getElementById('modal-title'); - const modalMessage = document.getElementById('modal-message'); - modalTitle.textContent = 'Spiel löschen'; - modalMessage.textContent = `Möchten Sie das Spiel zwischen ${game.player1} und ${game.player2} wirklich löschen?`; - modal.classList.add('show'); -} -function closeModal() { - const modal = document.getElementById('modal'); - modal.classList.remove('show'); - currentGameId = null; -} -function confirmDeleteGame(gameId) { - if (!gameId) return; - showLoading(); - setTimeout(() => { - const gameIndex = games.findIndex(g => g.id === gameId); - if (gameIndex !== -1) { - games.splice(gameIndex, 1); - saveGames(); - renderGames(); - if (currentGameId === gameId) { - showScreen('game-list-screen'); - } - } - hideLoading(); - closeModal(); - }, 500); -} - -// --- Game Rendering --- -function renderGames(filter = 'all') { - const gamesContainer = document.getElementById('games-container'); - const filteredGames = games - .filter(game => { - if (filter === 'active') return game.status === 'active'; - if (filter === 'completed') return game.status === 'completed'; - return true; - }) - .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - if (filteredGames.length === 0) { - gamesContainer.innerHTML = '
Keine Spiele vorhanden
'; - return; - } - gamesContainer.innerHTML = filteredGames.map(game => { - const playerNames = game.player3 - ? `${game.player1} vs ${game.player2} vs ${game.player3}` - : `${game.player1} vs ${game.player2}`; - const scores = game.player3 - ? `${game.score1} - ${game.score2} - ${game.score3}` - : `${game.score1} - ${game.score2}`; - return ` -
-
-
${game.gameType}${game.raceTo ? ` | ${game.raceTo}` : ''}
-
${playerNames}
-
${scores}
-
- -
- `; - }).join(''); -} - -// --- Game Detail --- -function showGameDetail(gameId) { - const game = games.find(g => g.id === gameId); - if (!game) return; - currentGameId = gameId; - document.getElementById('game-title').textContent = `${game.gameType}${game.raceTo ? ` | Race to ${game.raceTo}` : ''}`; - document.getElementById('player1-name').textContent = game.player1; - document.getElementById('player2-name').textContent = game.player2; - const player3Score = document.getElementById('player3-score'); - if (game.player3) { - document.getElementById('player3-name').textContent = game.player3; - document.getElementById('score3').textContent = game.score3; - player3Score.style.display = 'flex'; - } else { - player3Score.style.display = 'none'; - } - document.getElementById('score1').textContent = game.score1; - document.getElementById('score2').textContent = game.score2; - const player1Container = document.querySelector('.player-score:first-child'); - const player2Container = document.querySelector('.player-score:nth-child(2)'); - const player3Container = document.getElementById('player3-score'); - player1Container.classList.toggle('franky', game.player1 === 'Fränky'); - player2Container.classList.toggle('franky', game.player2 === 'Fränky'); - player3Container.classList.toggle('franky', game.player3 === 'Fränky'); - const controlButton = document.getElementById('game-control'); - if (game.status === 'completed') { - controlButton.textContent = 'Zurück zur Liste'; - controlButton.onclick = () => showScreen('game-list-screen'); - controlButton.classList.add('disabled'); - } else { - controlButton.textContent = 'Spiel beenden'; - controlButton.onclick = () => finishGame(); - controlButton.classList.remove('disabled'); - } - const scoreButtons = document.querySelectorAll('.score-button'); - scoreButtons.forEach(button => { - button.disabled = game.status === 'completed'; - }); - showScreen('game-detail-screen'); -} - -// --- Game Completion Modal --- -function showGameCompletionModal() { - const game = games.find(g => g.id === currentGameId); - if (!game) return; - const modal = document.getElementById('game-completion-modal'); - const finalScores = modal.querySelector('.final-scores'); - const winnerAnnouncement = modal.querySelector('.winner-announcement'); - let scoreHtml = ''; - scoreHtml += ` -
- ${game.player1} - ${game.score1} -
- `; - scoreHtml += ` -
- ${game.player2} - ${game.score2} -
- `; - if (game.player3) { - scoreHtml += ` -
- ${game.player3} - ${game.score3} -
- `; - } - finalScores.innerHTML = scoreHtml; - const scores = [game.score1, game.score2]; - if (game.player3) scores.push(game.score3); - const maxScore = Math.max(...scores); - const winners = []; - if (game.score1 === maxScore) winners.push(game.player1); - if (game.score2 === maxScore) winners.push(game.player2); - if (game.player3 && game.score3 === maxScore) winners.push(game.player3); - const winnerText = winners.length > 1 - ? `Unentschieden zwischen ${winners.join(' und ')}` - : `${winners[0]} hat gewonnen!`; - winnerAnnouncement.innerHTML = `

${winnerText}

`; - modal.classList.add('show'); -} -function closeGameCompletionModal() { - document.getElementById('game-completion-modal').classList.remove('show'); -} -function confirmGameCompletion() { - const game = games.find(g => g.id === currentGameId); - if (!game) return; - game.status = 'completed'; - game.updatedAt = new Date().toISOString(); - saveGames(); - renderGames(); - showGameDetail(currentGameId); - closeGameCompletionModal(); -} - -// --- Game Finish --- -function finishGame() { - const game = games.find(g => g.id === currentGameId); - if (!game) return; - showGameCompletionModal(); -} - -// --- Filter Games --- -function filterGames(filter) { - document.querySelectorAll('.filter-button').forEach(button => { - button.classList.remove('active'); - }); - document.querySelector(`.filter-button[onclick="filterGames('${filter}')"]`).classList.add('active'); - renderGames(filter); -} - -// --- Fullscreen --- -function toggleFullscreen() { - if (!document.fullscreenElement) { - document.documentElement.requestFullscreen().catch(err => { - console.log(`Error attempting to enable fullscreen: ${err.message}`); - }); - } else { - document.exitFullscreen(); - } -} -document.addEventListener('fullscreenchange', () => { - const button = document.getElementById('fullscreen-toggle'); - if (document.fullscreenElement) { - button.innerHTML = ``; - } else { - button.innerHTML = ``; - } -}); - -// --- Event Listeners --- -document.addEventListener('DOMContentLoaded', () => { - loadGames(); - // Attach event listeners for navigation, game creation, modals, etc. - document.querySelectorAll('.nav-button').forEach(btn => { - if (btn.textContent.includes('Neues Spiel')) btn.onclick = () => showScreen('new-game-screen'); - if (btn.textContent.includes('Abbrechen')) btn.onclick = () => showScreen('game-list-screen'); - if (btn.textContent.includes('Zurück zur Liste')) btn.onclick = () => showScreen('game-list-screen'); - }); - document.querySelectorAll('.filter-button').forEach((btn, idx) => { - if (btn.textContent === 'Alle') btn.onclick = () => filterGames('all'); - if (btn.textContent === 'Aktiv') btn.onclick = () => filterGames('active'); - if (btn.textContent === 'Abgeschlossen') btn.onclick = () => filterGames('completed'); - }); - document.getElementById('fullscreen-toggle').onclick = toggleFullscreen; - // Modal buttons - const modal = document.getElementById('modal'); - if (modal) { - modal.querySelector('.close-button').onclick = closeModal; - modal.querySelector('.modal-button.cancel').onclick = closeModal; - modal.querySelector('.modal-button.confirm').onclick = () => confirmDeleteGame(currentGameId); - } - const validationModal = document.getElementById('validation-modal'); - if (validationModal) { - validationModal.querySelector('.close-button').onclick = closeValidationModal; - validationModal.querySelector('.modal-button.cancel').onclick = closeValidationModal; - } - const gameCompletionModal = document.getElementById('game-completion-modal'); - if (gameCompletionModal) { - gameCompletionModal.querySelector('.btn.btn--warning').onclick = confirmGameCompletion; - gameCompletionModal.querySelector('.btn:not(.btn--warning)').onclick = closeGameCompletionModal; - } - // New game creation - const startGameBtn = document.querySelector('.nav-button'); - if (startGameBtn && startGameBtn.textContent.includes('Spiel starten')) { - startGameBtn.onclick = createNewGame; - } - // Player name selects - ['player1', 'player2', 'player3'].forEach(pid => { - const select = document.getElementById(`${pid}-select`); - if (select) select.onchange = () => updatePlayerInput(pid); - }); - // Score buttons - document.querySelectorAll('.score-button').forEach((btn, idx) => { - btn.onclick = () => { - const player = Math.floor(idx / 2) + 1; - const change = idx % 2 === 0 ? -1 : 1; - updateScore(currentGameId, player, change); - }; - }); -}); -// Initial screen -showScreen('game-list-screen'); - -window.toggleFullscreen = toggleFullscreen; \ No newline at end of file diff --git a/src/styles/index.css b/src/styles/index.css index 669845c..b50f47a 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -1,4 +1,4 @@ -/* Extracted from original index.astro