commit f79920ad6a206761d8cf53209da17789d37d1fdd Author: wsy182 <2392948297@qq.com> Date: Wed Feb 18 14:30:42 2026 +0800 first commit diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..5345049 --- /dev/null +++ b/.env.dev @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:8080/api/v1 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..5dda89b --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "mahjong-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.25", + "vue-router": "4" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.2", + "@vue/tsconfig": "^0.8.1", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vue-tsc": "^3.1.5" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..699d22a --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,970 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + vue: + specifier: ^3.5.25 + version: 3.5.28(typescript@5.9.3) + vue-router: + specifier: '4' + version: 4.6.4(vue@3.5.28(typescript@5.9.3)) + devDependencies: + '@types/node': + specifier: ^24.10.1 + version: 24.10.13 + '@vitejs/plugin-vue': + specifier: ^6.0.2 + version: 6.0.4(vite@7.3.1(@types/node@24.10.13))(vue@3.5.28(typescript@5.9.3)) + '@vue/tsconfig': + specifier: ^0.8.1 + version: 0.8.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@24.10.13) + vue-tsc: + specifier: ^3.1.5 + version: 3.2.4(typescript@5.9.3) + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rolldown/pluginutils@1.0.0-rc.2': + resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@24.10.13': + resolution: {integrity: sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==} + + '@vitejs/plugin-vue@6.0.4': + resolution: {integrity: sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vue: ^3.2.25 + + '@volar/language-core@2.4.27': + resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} + + '@volar/source-map@2.4.27': + resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} + + '@volar/typescript@2.4.27': + resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==} + + '@vue/compiler-core@3.5.28': + resolution: {integrity: sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==} + + '@vue/compiler-dom@3.5.28': + resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==} + + '@vue/compiler-sfc@3.5.28': + resolution: {integrity: sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==} + + '@vue/compiler-ssr@3.5.28': + resolution: {integrity: sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@3.2.4': + resolution: {integrity: sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==} + + '@vue/reactivity@3.5.28': + resolution: {integrity: sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==} + + '@vue/runtime-core@3.5.28': + resolution: {integrity: sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==} + + '@vue/runtime-dom@3.5.28': + resolution: {integrity: sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==} + + '@vue/server-renderer@3.5.28': + resolution: {integrity: sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==} + peerDependencies: + vue: 3.5.28 + + '@vue/shared@3.5.28': + resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==} + + '@vue/tsconfig@0.8.1': + resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + alien-signals@3.1.2: + resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@3.2.4: + resolution: {integrity: sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.28: + resolution: {integrity: sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rolldown/pluginutils@1.0.0-rc.2': {} + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@types/estree@1.0.8': {} + + '@types/node@24.10.13': + dependencies: + undici-types: 7.16.0 + + '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@24.10.13))(vue@3.5.28(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.2 + vite: 7.3.1(@types/node@24.10.13) + vue: 3.5.28(typescript@5.9.3) + + '@volar/language-core@2.4.27': + dependencies: + '@volar/source-map': 2.4.27 + + '@volar/source-map@2.4.27': {} + + '@volar/typescript@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.28': + dependencies: + '@babel/parser': 7.29.0 + '@vue/shared': 3.5.28 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.28': + dependencies: + '@vue/compiler-core': 3.5.28 + '@vue/shared': 3.5.28 + + '@vue/compiler-sfc@3.5.28': + dependencies: + '@babel/parser': 7.29.0 + '@vue/compiler-core': 3.5.28 + '@vue/compiler-dom': 3.5.28 + '@vue/compiler-ssr': 3.5.28 + '@vue/shared': 3.5.28 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.28': + dependencies: + '@vue/compiler-dom': 3.5.28 + '@vue/shared': 3.5.28 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@3.2.4': + dependencies: + '@volar/language-core': 2.4.27 + '@vue/compiler-dom': 3.5.28 + '@vue/shared': 3.5.28 + alien-signals: 3.1.2 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + + '@vue/reactivity@3.5.28': + dependencies: + '@vue/shared': 3.5.28 + + '@vue/runtime-core@3.5.28': + dependencies: + '@vue/reactivity': 3.5.28 + '@vue/shared': 3.5.28 + + '@vue/runtime-dom@3.5.28': + dependencies: + '@vue/reactivity': 3.5.28 + '@vue/runtime-core': 3.5.28 + '@vue/shared': 3.5.28 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.28(vue@3.5.28(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.28 + '@vue/shared': 3.5.28 + vue: 3.5.28(typescript@5.9.3) + + '@vue/shared@3.5.28': {} + + '@vue/tsconfig@0.8.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))': + optionalDependencies: + typescript: 5.9.3 + vue: 3.5.28(typescript@5.9.3) + + alien-signals@3.1.2: {} + + csstype@3.2.3: {} + + entities@7.0.1: {} + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fsevents@2.3.3: + optional: true + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + + source-map-js@1.2.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + vite@7.3.1(@types/node@24.10.13): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.13 + fsevents: 2.3.3 + + vscode-uri@3.1.0: {} + + vue-router@4.6.4(vue@3.5.28(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.28(typescript@5.9.3) + + vue-tsc@3.2.4(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.27 + '@vue/language-core': 3.2.4 + typescript: 5.9.3 + + vue@3.5.28(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.28 + '@vue/compiler-sfc': 3.5.28 + '@vue/runtime-dom': 3.5.28 + '@vue/server-renderer': 3.5.28(vue@3.5.28(typescript@5.9.3)) + '@vue/shared': 3.5.28 + optionalDependencies: + typescript: 5.9.3 diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..7c2aa3f --- /dev/null +++ b/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..eb5c1d6 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,185 @@ +export interface AuthUser { + id?: string | number + username?: string + nickname?: string +} + +export interface AuthSessionInput { + token: string + tokenType?: string + refreshToken?: string +} + +export interface AuthResult { + token: string + tokenType?: string + refreshToken?: string + expiresIn?: number + user?: AuthUser +} + +interface ApiErrorPayload { + message?: string + error?: string +} + +const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '').trim().replace(/\/$/, '') +const LOGIN_PATH = import.meta.env.VITE_LOGIN_PATH ?? '/api/v1/auth/login' +const REGISTER_PATH = import.meta.env.VITE_REGISTER_PATH ?? '/api/v1/auth/register' +const REFRESH_PATH = import.meta.env.VITE_REFRESH_PATH ?? '/api/v1/auth/refresh' +const LOGIN_BEARER_TOKEN = (import.meta.env.VITE_LOGIN_BEARER_TOKEN ?? '').trim() + +function buildUrl(path: string): string { + if (/^https?:\/\//.test(path)) { + return path + } + + const normalizedPath = path.startsWith('/') ? path : `/${path}` + if (!API_BASE_URL) { + return normalizedPath + } + + // Avoid duplicated API prefix, e.g. base: /api/v1 + path: /api/v1/auth/login + try { + const baseUrl = new URL(API_BASE_URL) + const basePath = baseUrl.pathname.replace(/\/$/, '') + if (basePath && normalizedPath.startsWith(`${basePath}/`)) { + return `${API_BASE_URL}${normalizedPath.slice(basePath.length)}` + } + } catch { + // API_BASE_URL may be a relative path; fallback to direct join. + } + + return `${API_BASE_URL}${normalizedPath}` +} + +async function request( + url: string, + body: Record, + extraHeaders?: Record, +): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...extraHeaders, + }, + body: JSON.stringify(body), + }) + + const payload = (await response.json().catch(() => ({}))) as T & ApiErrorPayload + if (!response.ok) { + throw new Error(payload.message ?? payload.error ?? '请求失败,请稍后再试') + } + + return payload +} + +function createAuthHeader(token: string, tokenType = 'Bearer'): string { + const normalizedToken = token.trim() + if (/^\S+\s+\S+/.test(normalizedToken)) { + return normalizedToken + } + + return `${tokenType || 'Bearer'} ${normalizedToken}` +} + +function extractToken(payload: Record): string { + const candidate = + payload.token ?? + payload.accessToken ?? + payload.access_token ?? + (payload.data as Record | undefined)?.token ?? + (payload.data as Record | undefined)?.accessToken ?? + (payload.data as Record | undefined)?.access_token + + if (typeof candidate !== 'string' || candidate.length === 0) { + throw new Error('登录成功,但后端未返回 token 字段') + } + + return candidate +} + +function extractTokenType(payload: Record): string | undefined { + const candidate = + payload.token_type ?? + payload.tokenType ?? + (payload.data as Record | undefined)?.token_type ?? + (payload.data as Record | undefined)?.tokenType + + return typeof candidate === 'string' && candidate.length > 0 ? candidate : undefined +} + +function extractRefreshToken(payload: Record): string | undefined { + const candidate = + payload.refresh_token ?? + payload.refreshToken ?? + (payload.data as Record | undefined)?.refresh_token ?? + (payload.data as Record | undefined)?.refreshToken + + return typeof candidate === 'string' && candidate.length > 0 ? candidate : undefined +} + +function extractExpiresIn(payload: Record): number | undefined { + const candidate = + payload.expires_in ?? + payload.expiresIn ?? + (payload.data as Record | undefined)?.expires_in ?? + (payload.data as Record | undefined)?.expiresIn + + return typeof candidate === 'number' && Number.isFinite(candidate) ? candidate : undefined +} + +function extractUser(payload: Record): AuthUser | undefined { + const user = payload.user ?? (payload.data as Record | undefined)?.user + return typeof user === 'object' && user !== null ? (user as AuthUser) : undefined +} + +function parseAuthResult(payload: Record): AuthResult { + return { + token: extractToken(payload), + tokenType: extractTokenType(payload), + refreshToken: extractRefreshToken(payload), + expiresIn: extractExpiresIn(payload), + user: extractUser(payload), + } +} + +export async function register(input: { + username: string + phone: string + email: string + password: string +}): Promise { + await request>(buildUrl(REGISTER_PATH), input) +} + +export async function login(input: { loginId: string; password: string }): Promise { + const payload = await request>( + buildUrl(LOGIN_PATH), + { + login_id: input.loginId, + password: input.password, + }, + LOGIN_BEARER_TOKEN ? { Authorization: `Bearer ${LOGIN_BEARER_TOKEN}` } : undefined, + ) + return parseAuthResult(payload) +} + +export async function refreshAccessToken(input: AuthSessionInput): Promise { + if (!input.refreshToken) { + throw new Error('缺少 refresh_token,无法刷新登录状态') + } + + const payload = await request>( + buildUrl(REFRESH_PATH), + { + refreshToken: input.refreshToken, + }, + { + Authorization: createAuthHeader(input.token, input.tokenType), + }, + ) + + return parseAuthResult(payload) +} diff --git a/src/api/authed-request.ts b/src/api/authed-request.ts new file mode 100644 index 0000000..1f6ec34 --- /dev/null +++ b/src/api/authed-request.ts @@ -0,0 +1,146 @@ +import { refreshAccessToken } from './auth' + +export interface AuthSession { + token: string + tokenType?: string + refreshToken?: string + expiresIn?: number +} + +export interface AuthedRequestOptions { + method: 'GET' | 'POST' + path: string + auth: AuthSession + body?: Record + onAuthUpdated?: (next: AuthSession) => void +} + +export interface ApiEnvelope { + code: number + msg: string + data: T +} + +interface ApiErrorPayload { + code?: number + msg?: string + message?: string + error?: string +} + +export class AuthExpiredError extends Error { + constructor(message = '登录状态已过期,请重新登录') { + super(message) + this.name = 'AuthExpiredError' + } +} + +const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '').trim().replace(/\/$/, '') + +function buildUrl(path: string): string { + if (/^https?:\/\//.test(path)) { + return path + } + + const normalizedPath = path.startsWith('/') ? path : `/${path}` + if (!API_BASE_URL) { + return normalizedPath + } + + try { + const baseUrl = new URL(API_BASE_URL) + const basePath = baseUrl.pathname.replace(/\/$/, '') + if (basePath && normalizedPath.startsWith(`${basePath}/`)) { + return `${API_BASE_URL}${normalizedPath.slice(basePath.length)}` + } + } catch { + // API_BASE_URL may be a relative path. + } + + return `${API_BASE_URL}${normalizedPath}` +} + +function createAuthHeader(token: string, tokenType = 'Bearer'): string { + const normalizedToken = token.trim() + if (/^\S+\s+\S+/.test(normalizedToken)) { + return normalizedToken + } + + return `${tokenType || 'Bearer'} ${normalizedToken}` +} + +async function parseEnvelope(response: Response): Promise> { + const payload = (await response.json().catch(() => ({}))) as ApiEnvelope & ApiErrorPayload + if (!response.ok) { + throw new Error(payload.msg ?? payload.message ?? payload.error ?? '请求失败,请稍后再试') + } + + if (typeof payload.code === 'number' && payload.code !== 0) { + throw new Error(payload.msg ?? '接口返回失败') + } + + return payload +} + +async function runRequest( + options: Omit & { session: AuthSession }, +): Promise<{ response: Response; parsed: ApiEnvelope }> { + const response = await fetch(buildUrl(options.path), { + method: options.method, + headers: { + ...(options.body ? { 'Content-Type': 'application/json' } : undefined), + Authorization: createAuthHeader(options.session.token, options.session.tokenType), + }, + body: options.body ? JSON.stringify(options.body) : undefined, + }) + + if (response.status === 401) { + return { + response, + parsed: { code: 401, msg: 'unauthorized', data: {} as T }, + } + } + + const parsed = await parseEnvelope(response) + return { response, parsed } +} + +export async function authedRequest(options: AuthedRequestOptions): Promise { + const first = await runRequest({ ...options, session: options.auth }) + if (first.response.status !== 401) { + return first.parsed.data + } + + if (!options.auth.refreshToken) { + throw new AuthExpiredError() + } + + try { + const refreshed = await refreshAccessToken({ + token: options.auth.token, + tokenType: options.auth.tokenType, + refreshToken: options.auth.refreshToken, + }) + + const nextAuth: AuthSession = { + token: refreshed.token, + tokenType: refreshed.tokenType ?? options.auth.tokenType, + refreshToken: refreshed.refreshToken ?? options.auth.refreshToken, + expiresIn: refreshed.expiresIn, + } + options.onAuthUpdated?.(nextAuth) + + const second = await runRequest({ ...options, session: nextAuth }) + if (second.response.status === 401) { + throw new AuthExpiredError() + } + + return second.parsed.data + } catch (error) { + if (error instanceof AuthExpiredError) { + throw error + } + + throw new AuthExpiredError() + } +} diff --git a/src/api/mahjong.ts b/src/api/mahjong.ts new file mode 100644 index 0000000..ba9059d --- /dev/null +++ b/src/api/mahjong.ts @@ -0,0 +1,71 @@ +import { authedRequest, type AuthSession } from './authed-request' + +export interface RoomItem { + room_id: string + name: string + game_type: string + owner_id: string + max_players: number + player_count: number + status: string + created_at: string + updated_at: string +} + +export interface RoomListResult { + items: RoomItem[] + page: number + size: number + total: number +} + +const ROOM_CREATE_PATH = + import.meta.env.VITE_ROOM_CREATE_PATH ?? '/api/v1/game/mahjong/room/create' +const ROOM_LIST_PATH = import.meta.env.VITE_ROOM_LIST_PATH ?? '/api/v1/game/mahjong/room/list' +const ROOM_JOIN_PATH = import.meta.env.VITE_ROOM_JOIN_PATH ?? '/api/v1/game/mahjong/room/join' + +export async function createRoom( + auth: AuthSession, + input: { name: string; gameType: string; maxPlayers: number }, + onAuthUpdated?: (next: AuthSession) => void, +): Promise { + return authedRequest({ + method: 'POST', + path: ROOM_CREATE_PATH, + auth, + onAuthUpdated, + body: { + name: input.name, + game_type: input.gameType, + max_players: input.maxPlayers, + }, + }) +} + +export async function listRooms( + auth: AuthSession, + onAuthUpdated?: (next: AuthSession) => void, +): Promise { + return authedRequest({ + method: 'GET', + path: ROOM_LIST_PATH, + auth, + onAuthUpdated, + }) +} + +export async function joinRoom( + auth: AuthSession, + input: { roomId: string }, + onAuthUpdated?: (next: AuthSession) => void, +): Promise { + await authedRequest | RoomItem>({ + method: 'POST', + path: ROOM_JOIN_PATH, + auth, + onAuthUpdated, + body: { + room_id: input.roomId, + }, + }) +} diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..3873579 --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,24 @@ +import { authedRequest, type AuthSession } from './authed-request' + +export interface UserInfo { + userID?: string + username?: string + phone?: string + email?: string + nickname?: string + [key: string]: unknown +} + +const USER_INFO_PATH = import.meta.env.VITE_USER_INFO_PATH ?? '/api/v1/user/info' + +export async function getUserInfo( + auth: AuthSession, + onAuthUpdated?: (next: AuthSession) => void, +): Promise { + return authedRequest({ + method: 'GET', + path: USER_INFO_PATH, + auth, + onAuthUpdated, + }) +} diff --git a/src/assets/images/desk/desk_01.png b/src/assets/images/desk/desk_01.png new file mode 100644 index 0000000..b0bd796 Binary files /dev/null and b/src/assets/images/desk/desk_01.png differ diff --git a/src/assets/images/flowerClolor/tiao.png b/src/assets/images/flowerClolor/tiao.png new file mode 100644 index 0000000..51b31a0 Binary files /dev/null and b/src/assets/images/flowerClolor/tiao.png differ diff --git a/src/assets/images/flowerClolor/tong.png b/src/assets/images/flowerClolor/tong.png new file mode 100644 index 0000000..d939386 Binary files /dev/null and b/src/assets/images/flowerClolor/tong.png differ diff --git a/src/assets/images/flowerClolor/wan.png b/src/assets/images/flowerClolor/wan.png new file mode 100644 index 0000000..27b9438 Binary files /dev/null and b/src/assets/images/flowerClolor/wan.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_1.png b/src/assets/images/tiles/bottom/p4b1_1.png new file mode 100644 index 0000000..53031fc Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_1.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_2.png b/src/assets/images/tiles/bottom/p4b1_2.png new file mode 100644 index 0000000..9d69f4b Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_2.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_3.png b/src/assets/images/tiles/bottom/p4b1_3.png new file mode 100644 index 0000000..365fe93 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_3.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_4.png b/src/assets/images/tiles/bottom/p4b1_4.png new file mode 100644 index 0000000..c57090f Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_4.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_5.png b/src/assets/images/tiles/bottom/p4b1_5.png new file mode 100644 index 0000000..5b8b9f6 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_5.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_6.png b/src/assets/images/tiles/bottom/p4b1_6.png new file mode 100644 index 0000000..2a14004 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_6.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_7.png b/src/assets/images/tiles/bottom/p4b1_7.png new file mode 100644 index 0000000..e20d4ac Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_7.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_8.png b/src/assets/images/tiles/bottom/p4b1_8.png new file mode 100644 index 0000000..312a847 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_8.png differ diff --git a/src/assets/images/tiles/bottom/p4b1_9.png b/src/assets/images/tiles/bottom/p4b1_9.png new file mode 100644 index 0000000..39b5cfd Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b1_9.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_1.png b/src/assets/images/tiles/bottom/p4b2_1.png new file mode 100644 index 0000000..46d8a79 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_1.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_2.png b/src/assets/images/tiles/bottom/p4b2_2.png new file mode 100644 index 0000000..38ca075 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_2.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_3.png b/src/assets/images/tiles/bottom/p4b2_3.png new file mode 100644 index 0000000..758b92b Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_3.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_4.png b/src/assets/images/tiles/bottom/p4b2_4.png new file mode 100644 index 0000000..60d9a34 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_4.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_5.png b/src/assets/images/tiles/bottom/p4b2_5.png new file mode 100644 index 0000000..e6ee225 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_5.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_6.png b/src/assets/images/tiles/bottom/p4b2_6.png new file mode 100644 index 0000000..573bece Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_6.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_7.png b/src/assets/images/tiles/bottom/p4b2_7.png new file mode 100644 index 0000000..2452c4a Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_7.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_8.png b/src/assets/images/tiles/bottom/p4b2_8.png new file mode 100644 index 0000000..5e4419b Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_8.png differ diff --git a/src/assets/images/tiles/bottom/p4b2_9.png b/src/assets/images/tiles/bottom/p4b2_9.png new file mode 100644 index 0000000..feecfdc Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b2_9.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_1.png b/src/assets/images/tiles/bottom/p4b3_1.png new file mode 100644 index 0000000..930a590 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_1.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_2.png b/src/assets/images/tiles/bottom/p4b3_2.png new file mode 100644 index 0000000..3ba0423 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_2.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_3.png b/src/assets/images/tiles/bottom/p4b3_3.png new file mode 100644 index 0000000..9eab3e3 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_3.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_4.png b/src/assets/images/tiles/bottom/p4b3_4.png new file mode 100644 index 0000000..49bfd13 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_4.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_5.png b/src/assets/images/tiles/bottom/p4b3_5.png new file mode 100644 index 0000000..5bf9697 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_5.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_6.png b/src/assets/images/tiles/bottom/p4b3_6.png new file mode 100644 index 0000000..797f680 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_6.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_7.png b/src/assets/images/tiles/bottom/p4b3_7.png new file mode 100644 index 0000000..d28ef3e Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_7.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_8.png b/src/assets/images/tiles/bottom/p4b3_8.png new file mode 100644 index 0000000..07e8bcf Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_8.png differ diff --git a/src/assets/images/tiles/bottom/p4b3_9.png b/src/assets/images/tiles/bottom/p4b3_9.png new file mode 100644 index 0000000..296d3db Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b3_9.png differ diff --git a/src/assets/images/tiles/bottom/p4b4_1.png b/src/assets/images/tiles/bottom/p4b4_1.png new file mode 100644 index 0000000..f8b0680 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b4_1.png differ diff --git a/src/assets/images/tiles/bottom/p4b4_2.png b/src/assets/images/tiles/bottom/p4b4_2.png new file mode 100644 index 0000000..5577351 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b4_2.png differ diff --git a/src/assets/images/tiles/bottom/p4b4_3.png b/src/assets/images/tiles/bottom/p4b4_3.png new file mode 100644 index 0000000..be230f2 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b4_3.png differ diff --git a/src/assets/images/tiles/bottom/p4b4_4.png b/src/assets/images/tiles/bottom/p4b4_4.png new file mode 100644 index 0000000..0931766 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b4_4.png differ diff --git a/src/assets/images/tiles/bottom/p4b4_5.png b/src/assets/images/tiles/bottom/p4b4_5.png new file mode 100644 index 0000000..3b2d44b Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b4_5.png differ diff --git a/src/assets/images/tiles/bottom/p4b4_6.png b/src/assets/images/tiles/bottom/p4b4_6.png new file mode 100644 index 0000000..ff4c70f Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b4_6.png differ diff --git a/src/assets/images/tiles/bottom/p4b4_7.png b/src/assets/images/tiles/bottom/p4b4_7.png new file mode 100644 index 0000000..1b54f7f Binary files /dev/null and b/src/assets/images/tiles/bottom/p4b4_7.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_1.png b/src/assets/images/tiles/bottom/p4s1_1.png new file mode 100644 index 0000000..5fd21ee Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_1.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_2.png b/src/assets/images/tiles/bottom/p4s1_2.png new file mode 100644 index 0000000..8b8f145 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_2.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_3.png b/src/assets/images/tiles/bottom/p4s1_3.png new file mode 100644 index 0000000..885c820 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_3.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_4.png b/src/assets/images/tiles/bottom/p4s1_4.png new file mode 100644 index 0000000..bc4b0b2 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_4.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_5.png b/src/assets/images/tiles/bottom/p4s1_5.png new file mode 100644 index 0000000..f335e98 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_5.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_6.png b/src/assets/images/tiles/bottom/p4s1_6.png new file mode 100644 index 0000000..05d7b4a Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_6.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_7.png b/src/assets/images/tiles/bottom/p4s1_7.png new file mode 100644 index 0000000..fa44f87 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_7.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_8.png b/src/assets/images/tiles/bottom/p4s1_8.png new file mode 100644 index 0000000..5f4dd94 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_8.png differ diff --git a/src/assets/images/tiles/bottom/p4s1_9.png b/src/assets/images/tiles/bottom/p4s1_9.png new file mode 100644 index 0000000..2fae95e Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s1_9.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_1.png b/src/assets/images/tiles/bottom/p4s2_1.png new file mode 100644 index 0000000..9534386 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_1.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_2.png b/src/assets/images/tiles/bottom/p4s2_2.png new file mode 100644 index 0000000..cbfe470 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_2.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_3.png b/src/assets/images/tiles/bottom/p4s2_3.png new file mode 100644 index 0000000..e2b27e4 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_3.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_4.png b/src/assets/images/tiles/bottom/p4s2_4.png new file mode 100644 index 0000000..e7a5fd5 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_4.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_5.png b/src/assets/images/tiles/bottom/p4s2_5.png new file mode 100644 index 0000000..79587cf Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_5.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_6.png b/src/assets/images/tiles/bottom/p4s2_6.png new file mode 100644 index 0000000..701946c Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_6.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_7.png b/src/assets/images/tiles/bottom/p4s2_7.png new file mode 100644 index 0000000..560e264 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_7.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_8.png b/src/assets/images/tiles/bottom/p4s2_8.png new file mode 100644 index 0000000..f49a042 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_8.png differ diff --git a/src/assets/images/tiles/bottom/p4s2_9.png b/src/assets/images/tiles/bottom/p4s2_9.png new file mode 100644 index 0000000..0ecc90c Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s2_9.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_1.png b/src/assets/images/tiles/bottom/p4s3_1.png new file mode 100644 index 0000000..86f47f2 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_1.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_2.png b/src/assets/images/tiles/bottom/p4s3_2.png new file mode 100644 index 0000000..f2da423 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_2.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_3.png b/src/assets/images/tiles/bottom/p4s3_3.png new file mode 100644 index 0000000..8acb19c Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_3.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_4.png b/src/assets/images/tiles/bottom/p4s3_4.png new file mode 100644 index 0000000..8a141f6 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_4.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_5.png b/src/assets/images/tiles/bottom/p4s3_5.png new file mode 100644 index 0000000..4c000a9 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_5.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_6.png b/src/assets/images/tiles/bottom/p4s3_6.png new file mode 100644 index 0000000..8a7e1c7 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_6.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_7.png b/src/assets/images/tiles/bottom/p4s3_7.png new file mode 100644 index 0000000..30dec73 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_7.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_8.png b/src/assets/images/tiles/bottom/p4s3_8.png new file mode 100644 index 0000000..0fabaa7 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_8.png differ diff --git a/src/assets/images/tiles/bottom/p4s3_9.png b/src/assets/images/tiles/bottom/p4s3_9.png new file mode 100644 index 0000000..875b907 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s3_9.png differ diff --git a/src/assets/images/tiles/bottom/p4s4_1.png b/src/assets/images/tiles/bottom/p4s4_1.png new file mode 100644 index 0000000..7d52844 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s4_1.png differ diff --git a/src/assets/images/tiles/bottom/p4s4_2.png b/src/assets/images/tiles/bottom/p4s4_2.png new file mode 100644 index 0000000..bec3c65 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s4_2.png differ diff --git a/src/assets/images/tiles/bottom/p4s4_3.png b/src/assets/images/tiles/bottom/p4s4_3.png new file mode 100644 index 0000000..44ebb0d Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s4_3.png differ diff --git a/src/assets/images/tiles/bottom/p4s4_4.png b/src/assets/images/tiles/bottom/p4s4_4.png new file mode 100644 index 0000000..2b83a3b Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s4_4.png differ diff --git a/src/assets/images/tiles/bottom/p4s4_5.png b/src/assets/images/tiles/bottom/p4s4_5.png new file mode 100644 index 0000000..2009fff Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s4_5.png differ diff --git a/src/assets/images/tiles/bottom/p4s4_6.png b/src/assets/images/tiles/bottom/p4s4_6.png new file mode 100644 index 0000000..b316692 Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s4_6.png differ diff --git a/src/assets/images/tiles/bottom/p4s4_7.png b/src/assets/images/tiles/bottom/p4s4_7.png new file mode 100644 index 0000000..8b8f9dd Binary files /dev/null and b/src/assets/images/tiles/bottom/p4s4_7.png differ diff --git a/src/assets/images/tiles/bottom/tdbgs_4.png b/src/assets/images/tiles/bottom/tdbgs_4.png new file mode 100644 index 0000000..9c79180 Binary files /dev/null and b/src/assets/images/tiles/bottom/tdbgs_4.png differ diff --git a/src/assets/images/tiles/left/p3s1_1.png b/src/assets/images/tiles/left/p3s1_1.png new file mode 100644 index 0000000..ed8ac87 Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_1.png differ diff --git a/src/assets/images/tiles/left/p3s1_2.png b/src/assets/images/tiles/left/p3s1_2.png new file mode 100644 index 0000000..1d1ce35 Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_2.png differ diff --git a/src/assets/images/tiles/left/p3s1_3.png b/src/assets/images/tiles/left/p3s1_3.png new file mode 100644 index 0000000..84d606e Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_3.png differ diff --git a/src/assets/images/tiles/left/p3s1_4.png b/src/assets/images/tiles/left/p3s1_4.png new file mode 100644 index 0000000..d13c536 Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_4.png differ diff --git a/src/assets/images/tiles/left/p3s1_5.png b/src/assets/images/tiles/left/p3s1_5.png new file mode 100644 index 0000000..a1830b3 Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_5.png differ diff --git a/src/assets/images/tiles/left/p3s1_6.png b/src/assets/images/tiles/left/p3s1_6.png new file mode 100644 index 0000000..79322c0 Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_6.png differ diff --git a/src/assets/images/tiles/left/p3s1_7.png b/src/assets/images/tiles/left/p3s1_7.png new file mode 100644 index 0000000..40a9946 Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_7.png differ diff --git a/src/assets/images/tiles/left/p3s1_8.png b/src/assets/images/tiles/left/p3s1_8.png new file mode 100644 index 0000000..0dc4c90 Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_8.png differ diff --git a/src/assets/images/tiles/left/p3s1_9.png b/src/assets/images/tiles/left/p3s1_9.png new file mode 100644 index 0000000..a6ee04c Binary files /dev/null and b/src/assets/images/tiles/left/p3s1_9.png differ diff --git a/src/assets/images/tiles/left/p3s2_1.png b/src/assets/images/tiles/left/p3s2_1.png new file mode 100644 index 0000000..33a54fb Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_1.png differ diff --git a/src/assets/images/tiles/left/p3s2_2.png b/src/assets/images/tiles/left/p3s2_2.png new file mode 100644 index 0000000..d856157 Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_2.png differ diff --git a/src/assets/images/tiles/left/p3s2_3.png b/src/assets/images/tiles/left/p3s2_3.png new file mode 100644 index 0000000..41f1618 Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_3.png differ diff --git a/src/assets/images/tiles/left/p3s2_4.png b/src/assets/images/tiles/left/p3s2_4.png new file mode 100644 index 0000000..139d8dc Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_4.png differ diff --git a/src/assets/images/tiles/left/p3s2_5.png b/src/assets/images/tiles/left/p3s2_5.png new file mode 100644 index 0000000..aac7f70 Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_5.png differ diff --git a/src/assets/images/tiles/left/p3s2_6.png b/src/assets/images/tiles/left/p3s2_6.png new file mode 100644 index 0000000..0cd0c2d Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_6.png differ diff --git a/src/assets/images/tiles/left/p3s2_7.png b/src/assets/images/tiles/left/p3s2_7.png new file mode 100644 index 0000000..2b66350 Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_7.png differ diff --git a/src/assets/images/tiles/left/p3s2_8.png b/src/assets/images/tiles/left/p3s2_8.png new file mode 100644 index 0000000..69346fc Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_8.png differ diff --git a/src/assets/images/tiles/left/p3s2_9.png b/src/assets/images/tiles/left/p3s2_9.png new file mode 100644 index 0000000..3cdf4bc Binary files /dev/null and b/src/assets/images/tiles/left/p3s2_9.png differ diff --git a/src/assets/images/tiles/left/p3s3_1.png b/src/assets/images/tiles/left/p3s3_1.png new file mode 100644 index 0000000..1459c37 Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_1.png differ diff --git a/src/assets/images/tiles/left/p3s3_2.png b/src/assets/images/tiles/left/p3s3_2.png new file mode 100644 index 0000000..d9660fc Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_2.png differ diff --git a/src/assets/images/tiles/left/p3s3_3.png b/src/assets/images/tiles/left/p3s3_3.png new file mode 100644 index 0000000..019ab07 Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_3.png differ diff --git a/src/assets/images/tiles/left/p3s3_4.png b/src/assets/images/tiles/left/p3s3_4.png new file mode 100644 index 0000000..294890d Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_4.png differ diff --git a/src/assets/images/tiles/left/p3s3_5.png b/src/assets/images/tiles/left/p3s3_5.png new file mode 100644 index 0000000..c2d7032 Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_5.png differ diff --git a/src/assets/images/tiles/left/p3s3_6.png b/src/assets/images/tiles/left/p3s3_6.png new file mode 100644 index 0000000..3536c02 Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_6.png differ diff --git a/src/assets/images/tiles/left/p3s3_7.png b/src/assets/images/tiles/left/p3s3_7.png new file mode 100644 index 0000000..6521aea Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_7.png differ diff --git a/src/assets/images/tiles/left/p3s3_8.png b/src/assets/images/tiles/left/p3s3_8.png new file mode 100644 index 0000000..b55fa67 Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_8.png differ diff --git a/src/assets/images/tiles/left/p3s3_9.png b/src/assets/images/tiles/left/p3s3_9.png new file mode 100644 index 0000000..8fec8fc Binary files /dev/null and b/src/assets/images/tiles/left/p3s3_9.png differ diff --git a/src/assets/images/tiles/left/p3s4_1.png b/src/assets/images/tiles/left/p3s4_1.png new file mode 100644 index 0000000..082047e Binary files /dev/null and b/src/assets/images/tiles/left/p3s4_1.png differ diff --git a/src/assets/images/tiles/left/p3s4_2.png b/src/assets/images/tiles/left/p3s4_2.png new file mode 100644 index 0000000..4324743 Binary files /dev/null and b/src/assets/images/tiles/left/p3s4_2.png differ diff --git a/src/assets/images/tiles/left/p3s4_3.png b/src/assets/images/tiles/left/p3s4_3.png new file mode 100644 index 0000000..6c7b9ca Binary files /dev/null and b/src/assets/images/tiles/left/p3s4_3.png differ diff --git a/src/assets/images/tiles/left/p3s4_4.png b/src/assets/images/tiles/left/p3s4_4.png new file mode 100644 index 0000000..5e2e709 Binary files /dev/null and b/src/assets/images/tiles/left/p3s4_4.png differ diff --git a/src/assets/images/tiles/left/p3s4_5.png b/src/assets/images/tiles/left/p3s4_5.png new file mode 100644 index 0000000..a09dbd3 Binary files /dev/null and b/src/assets/images/tiles/left/p3s4_5.png differ diff --git a/src/assets/images/tiles/left/p3s4_6.png b/src/assets/images/tiles/left/p3s4_6.png new file mode 100644 index 0000000..d551695 Binary files /dev/null and b/src/assets/images/tiles/left/p3s4_6.png differ diff --git a/src/assets/images/tiles/left/p3s4_7.png b/src/assets/images/tiles/left/p3s4_7.png new file mode 100644 index 0000000..5501b00 Binary files /dev/null and b/src/assets/images/tiles/left/p3s4_7.png differ diff --git a/src/assets/images/tiles/left/tbgs_3.png b/src/assets/images/tiles/left/tbgs_3.png new file mode 100644 index 0000000..eee18e2 Binary files /dev/null and b/src/assets/images/tiles/left/tbgs_3.png differ diff --git a/src/assets/images/tiles/left/tdbgs_3.png b/src/assets/images/tiles/left/tdbgs_3.png new file mode 100644 index 0000000..1c293a3 Binary files /dev/null and b/src/assets/images/tiles/left/tdbgs_3.png differ diff --git a/src/assets/images/tiles/right/p1s1_1.png b/src/assets/images/tiles/right/p1s1_1.png new file mode 100644 index 0000000..c8739ce Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_1.png differ diff --git a/src/assets/images/tiles/right/p1s1_2.png b/src/assets/images/tiles/right/p1s1_2.png new file mode 100644 index 0000000..314c8f7 Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_2.png differ diff --git a/src/assets/images/tiles/right/p1s1_3.png b/src/assets/images/tiles/right/p1s1_3.png new file mode 100644 index 0000000..bcddef1 Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_3.png differ diff --git a/src/assets/images/tiles/right/p1s1_4.png b/src/assets/images/tiles/right/p1s1_4.png new file mode 100644 index 0000000..708dad8 Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_4.png differ diff --git a/src/assets/images/tiles/right/p1s1_5.png b/src/assets/images/tiles/right/p1s1_5.png new file mode 100644 index 0000000..d48bcff Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_5.png differ diff --git a/src/assets/images/tiles/right/p1s1_6.png b/src/assets/images/tiles/right/p1s1_6.png new file mode 100644 index 0000000..a839e34 Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_6.png differ diff --git a/src/assets/images/tiles/right/p1s1_7.png b/src/assets/images/tiles/right/p1s1_7.png new file mode 100644 index 0000000..f36d850 Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_7.png differ diff --git a/src/assets/images/tiles/right/p1s1_8.png b/src/assets/images/tiles/right/p1s1_8.png new file mode 100644 index 0000000..bce6a1e Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_8.png differ diff --git a/src/assets/images/tiles/right/p1s1_9.png b/src/assets/images/tiles/right/p1s1_9.png new file mode 100644 index 0000000..4ed6d3f Binary files /dev/null and b/src/assets/images/tiles/right/p1s1_9.png differ diff --git a/src/assets/images/tiles/right/p1s2_1.png b/src/assets/images/tiles/right/p1s2_1.png new file mode 100644 index 0000000..54dd4e0 Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_1.png differ diff --git a/src/assets/images/tiles/right/p1s2_2.png b/src/assets/images/tiles/right/p1s2_2.png new file mode 100644 index 0000000..a9b5a49 Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_2.png differ diff --git a/src/assets/images/tiles/right/p1s2_3.png b/src/assets/images/tiles/right/p1s2_3.png new file mode 100644 index 0000000..a896f0b Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_3.png differ diff --git a/src/assets/images/tiles/right/p1s2_4.png b/src/assets/images/tiles/right/p1s2_4.png new file mode 100644 index 0000000..2b7cd8a Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_4.png differ diff --git a/src/assets/images/tiles/right/p1s2_5.png b/src/assets/images/tiles/right/p1s2_5.png new file mode 100644 index 0000000..c2db1de Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_5.png differ diff --git a/src/assets/images/tiles/right/p1s2_6.png b/src/assets/images/tiles/right/p1s2_6.png new file mode 100644 index 0000000..0dbdeb2 Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_6.png differ diff --git a/src/assets/images/tiles/right/p1s2_7.png b/src/assets/images/tiles/right/p1s2_7.png new file mode 100644 index 0000000..5f4d44c Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_7.png differ diff --git a/src/assets/images/tiles/right/p1s2_8.png b/src/assets/images/tiles/right/p1s2_8.png new file mode 100644 index 0000000..9ea7441 Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_8.png differ diff --git a/src/assets/images/tiles/right/p1s2_9.png b/src/assets/images/tiles/right/p1s2_9.png new file mode 100644 index 0000000..304744b Binary files /dev/null and b/src/assets/images/tiles/right/p1s2_9.png differ diff --git a/src/assets/images/tiles/right/p1s3_1.png b/src/assets/images/tiles/right/p1s3_1.png new file mode 100644 index 0000000..99ce1e8 Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_1.png differ diff --git a/src/assets/images/tiles/right/p1s3_2.png b/src/assets/images/tiles/right/p1s3_2.png new file mode 100644 index 0000000..3aab65f Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_2.png differ diff --git a/src/assets/images/tiles/right/p1s3_3.png b/src/assets/images/tiles/right/p1s3_3.png new file mode 100644 index 0000000..944246b Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_3.png differ diff --git a/src/assets/images/tiles/right/p1s3_4.png b/src/assets/images/tiles/right/p1s3_4.png new file mode 100644 index 0000000..9cd03e4 Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_4.png differ diff --git a/src/assets/images/tiles/right/p1s3_5.png b/src/assets/images/tiles/right/p1s3_5.png new file mode 100644 index 0000000..6bf99d2 Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_5.png differ diff --git a/src/assets/images/tiles/right/p1s3_6.png b/src/assets/images/tiles/right/p1s3_6.png new file mode 100644 index 0000000..8fc1bc0 Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_6.png differ diff --git a/src/assets/images/tiles/right/p1s3_7.png b/src/assets/images/tiles/right/p1s3_7.png new file mode 100644 index 0000000..95f9944 Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_7.png differ diff --git a/src/assets/images/tiles/right/p1s3_8.png b/src/assets/images/tiles/right/p1s3_8.png new file mode 100644 index 0000000..a05113b Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_8.png differ diff --git a/src/assets/images/tiles/right/p1s3_9.png b/src/assets/images/tiles/right/p1s3_9.png new file mode 100644 index 0000000..d160289 Binary files /dev/null and b/src/assets/images/tiles/right/p1s3_9.png differ diff --git a/src/assets/images/tiles/right/p1s4_1.png b/src/assets/images/tiles/right/p1s4_1.png new file mode 100644 index 0000000..979f453 Binary files /dev/null and b/src/assets/images/tiles/right/p1s4_1.png differ diff --git a/src/assets/images/tiles/right/p1s4_2.png b/src/assets/images/tiles/right/p1s4_2.png new file mode 100644 index 0000000..a6617ca Binary files /dev/null and b/src/assets/images/tiles/right/p1s4_2.png differ diff --git a/src/assets/images/tiles/right/p1s4_3.png b/src/assets/images/tiles/right/p1s4_3.png new file mode 100644 index 0000000..b2930cf Binary files /dev/null and b/src/assets/images/tiles/right/p1s4_3.png differ diff --git a/src/assets/images/tiles/right/p1s4_4.png b/src/assets/images/tiles/right/p1s4_4.png new file mode 100644 index 0000000..a618ff7 Binary files /dev/null and b/src/assets/images/tiles/right/p1s4_4.png differ diff --git a/src/assets/images/tiles/right/p1s4_5.png b/src/assets/images/tiles/right/p1s4_5.png new file mode 100644 index 0000000..36cf636 Binary files /dev/null and b/src/assets/images/tiles/right/p1s4_5.png differ diff --git a/src/assets/images/tiles/right/p1s4_6.png b/src/assets/images/tiles/right/p1s4_6.png new file mode 100644 index 0000000..67393c9 Binary files /dev/null and b/src/assets/images/tiles/right/p1s4_6.png differ diff --git a/src/assets/images/tiles/right/p1s4_7.png b/src/assets/images/tiles/right/p1s4_7.png new file mode 100644 index 0000000..81e0c2c Binary files /dev/null and b/src/assets/images/tiles/right/p1s4_7.png differ diff --git a/src/assets/images/tiles/right/tbgs_1.png b/src/assets/images/tiles/right/tbgs_1.png new file mode 100644 index 0000000..05f69fe Binary files /dev/null and b/src/assets/images/tiles/right/tbgs_1.png differ diff --git a/src/assets/images/tiles/right/tdbgs_1.png b/src/assets/images/tiles/right/tdbgs_1.png new file mode 100644 index 0000000..3d16da8 Binary files /dev/null and b/src/assets/images/tiles/right/tdbgs_1.png differ diff --git a/src/assets/images/tiles/top/p2s1_1.png b/src/assets/images/tiles/top/p2s1_1.png new file mode 100644 index 0000000..4cadd96 Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_1.png differ diff --git a/src/assets/images/tiles/top/p2s1_2.png b/src/assets/images/tiles/top/p2s1_2.png new file mode 100644 index 0000000..3dc1162 Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_2.png differ diff --git a/src/assets/images/tiles/top/p2s1_3.png b/src/assets/images/tiles/top/p2s1_3.png new file mode 100644 index 0000000..11a3fd1 Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_3.png differ diff --git a/src/assets/images/tiles/top/p2s1_4.png b/src/assets/images/tiles/top/p2s1_4.png new file mode 100644 index 0000000..7ace610 Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_4.png differ diff --git a/src/assets/images/tiles/top/p2s1_5.png b/src/assets/images/tiles/top/p2s1_5.png new file mode 100644 index 0000000..3adcab9 Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_5.png differ diff --git a/src/assets/images/tiles/top/p2s1_6.png b/src/assets/images/tiles/top/p2s1_6.png new file mode 100644 index 0000000..ab97817 Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_6.png differ diff --git a/src/assets/images/tiles/top/p2s1_7.png b/src/assets/images/tiles/top/p2s1_7.png new file mode 100644 index 0000000..b6f34cd Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_7.png differ diff --git a/src/assets/images/tiles/top/p2s1_8.png b/src/assets/images/tiles/top/p2s1_8.png new file mode 100644 index 0000000..9d33b97 Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_8.png differ diff --git a/src/assets/images/tiles/top/p2s1_9.png b/src/assets/images/tiles/top/p2s1_9.png new file mode 100644 index 0000000..bec3b2a Binary files /dev/null and b/src/assets/images/tiles/top/p2s1_9.png differ diff --git a/src/assets/images/tiles/top/p2s2_1.png b/src/assets/images/tiles/top/p2s2_1.png new file mode 100644 index 0000000..6e77098 Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_1.png differ diff --git a/src/assets/images/tiles/top/p2s2_2.png b/src/assets/images/tiles/top/p2s2_2.png new file mode 100644 index 0000000..26f6417 Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_2.png differ diff --git a/src/assets/images/tiles/top/p2s2_3.png b/src/assets/images/tiles/top/p2s2_3.png new file mode 100644 index 0000000..089609a Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_3.png differ diff --git a/src/assets/images/tiles/top/p2s2_4.png b/src/assets/images/tiles/top/p2s2_4.png new file mode 100644 index 0000000..c5869d8 Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_4.png differ diff --git a/src/assets/images/tiles/top/p2s2_5.png b/src/assets/images/tiles/top/p2s2_5.png new file mode 100644 index 0000000..51a11da Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_5.png differ diff --git a/src/assets/images/tiles/top/p2s2_6.png b/src/assets/images/tiles/top/p2s2_6.png new file mode 100644 index 0000000..ebf0c2f Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_6.png differ diff --git a/src/assets/images/tiles/top/p2s2_7.png b/src/assets/images/tiles/top/p2s2_7.png new file mode 100644 index 0000000..be5f9b0 Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_7.png differ diff --git a/src/assets/images/tiles/top/p2s2_8.png b/src/assets/images/tiles/top/p2s2_8.png new file mode 100644 index 0000000..0525de2 Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_8.png differ diff --git a/src/assets/images/tiles/top/p2s2_9.png b/src/assets/images/tiles/top/p2s2_9.png new file mode 100644 index 0000000..fe4547c Binary files /dev/null and b/src/assets/images/tiles/top/p2s2_9.png differ diff --git a/src/assets/images/tiles/top/p2s3_1.png b/src/assets/images/tiles/top/p2s3_1.png new file mode 100644 index 0000000..12f330b Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_1.png differ diff --git a/src/assets/images/tiles/top/p2s3_2.png b/src/assets/images/tiles/top/p2s3_2.png new file mode 100644 index 0000000..e09bf5c Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_2.png differ diff --git a/src/assets/images/tiles/top/p2s3_3.png b/src/assets/images/tiles/top/p2s3_3.png new file mode 100644 index 0000000..9a71b7e Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_3.png differ diff --git a/src/assets/images/tiles/top/p2s3_4.png b/src/assets/images/tiles/top/p2s3_4.png new file mode 100644 index 0000000..cbeacf4 Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_4.png differ diff --git a/src/assets/images/tiles/top/p2s3_5.png b/src/assets/images/tiles/top/p2s3_5.png new file mode 100644 index 0000000..cbe98b6 Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_5.png differ diff --git a/src/assets/images/tiles/top/p2s3_6.png b/src/assets/images/tiles/top/p2s3_6.png new file mode 100644 index 0000000..72015c7 Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_6.png differ diff --git a/src/assets/images/tiles/top/p2s3_7.png b/src/assets/images/tiles/top/p2s3_7.png new file mode 100644 index 0000000..54ec2f0 Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_7.png differ diff --git a/src/assets/images/tiles/top/p2s3_8.png b/src/assets/images/tiles/top/p2s3_8.png new file mode 100644 index 0000000..58f520c Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_8.png differ diff --git a/src/assets/images/tiles/top/p2s3_9.png b/src/assets/images/tiles/top/p2s3_9.png new file mode 100644 index 0000000..3171270 Binary files /dev/null and b/src/assets/images/tiles/top/p2s3_9.png differ diff --git a/src/assets/images/tiles/top/p2s4_1.png b/src/assets/images/tiles/top/p2s4_1.png new file mode 100644 index 0000000..17282cd Binary files /dev/null and b/src/assets/images/tiles/top/p2s4_1.png differ diff --git a/src/assets/images/tiles/top/p2s4_2.png b/src/assets/images/tiles/top/p2s4_2.png new file mode 100644 index 0000000..7e61dd9 Binary files /dev/null and b/src/assets/images/tiles/top/p2s4_2.png differ diff --git a/src/assets/images/tiles/top/p2s4_3.png b/src/assets/images/tiles/top/p2s4_3.png new file mode 100644 index 0000000..4ff0b02 Binary files /dev/null and b/src/assets/images/tiles/top/p2s4_3.png differ diff --git a/src/assets/images/tiles/top/p2s4_4.png b/src/assets/images/tiles/top/p2s4_4.png new file mode 100644 index 0000000..ef86c1f Binary files /dev/null and b/src/assets/images/tiles/top/p2s4_4.png differ diff --git a/src/assets/images/tiles/top/p2s4_5.png b/src/assets/images/tiles/top/p2s4_5.png new file mode 100644 index 0000000..d674f11 Binary files /dev/null and b/src/assets/images/tiles/top/p2s4_5.png differ diff --git a/src/assets/images/tiles/top/p2s4_6.png b/src/assets/images/tiles/top/p2s4_6.png new file mode 100644 index 0000000..b693322 Binary files /dev/null and b/src/assets/images/tiles/top/p2s4_6.png differ diff --git a/src/assets/images/tiles/top/p2s4_7.png b/src/assets/images/tiles/top/p2s4_7.png new file mode 100644 index 0000000..7757d8d Binary files /dev/null and b/src/assets/images/tiles/top/p2s4_7.png differ diff --git a/src/assets/images/tiles/top/tbgs_2.png b/src/assets/images/tiles/top/tbgs_2.png new file mode 100644 index 0000000..a8c50b2 Binary files /dev/null and b/src/assets/images/tiles/top/tbgs_2.png differ diff --git a/src/assets/images/tiles/top/tdbgs_2.png b/src/assets/images/tiles/top/tdbgs_2.png new file mode 100644 index 0000000..c6b7a9f Binary files /dev/null and b/src/assets/images/tiles/top/tdbgs_2.png differ diff --git a/src/assets/styles/animation.css b/src/assets/styles/animation.css new file mode 100644 index 0000000..1babdc0 --- /dev/null +++ b/src/assets/styles/animation.css @@ -0,0 +1,29 @@ +@keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(8px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes pulse-soft { + 0% { + box-shadow: 0 0 0 0 rgba(30, 107, 76, 0.35); + } + + 70% { + box-shadow: 0 0 0 10px rgba(30, 107, 76, 0); + } + + 100% { + box-shadow: 0 0 0 0 rgba(30, 107, 76, 0); + } +} + +.animate-in { + animation: fade-in-up 220ms ease-out; +} diff --git a/src/assets/styles/global.css b/src/assets/styles/global.css new file mode 100644 index 0000000..a0e19c4 --- /dev/null +++ b/src/assets/styles/global.css @@ -0,0 +1,851 @@ +:root { + font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; + line-height: 1.5; + font-weight: 400; + color: #f7f0dc; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + background: radial-gradient(circle at 12% 12%, #254935 0%, #11251c 45%, #0a1411 100%); +} + +#app { + min-height: 100vh; +} + +h1, +h2, +p { + margin: 0; +} + +.app-shell { + min-height: 100vh; +} + +.auth-page { + display: grid; + place-items: center; + min-height: 100vh; + padding: 24px; +} + +.auth-card { + width: min(440px, 100%); + padding: 28px 24px; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.16); + background: rgba(8, 27, 20, 0.82); + backdrop-filter: blur(8px); + box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35); +} + +.auth-card h1 { + font-size: 30px; + text-align: center; +} + +.sub-title { + margin-top: 8px; + color: #c0ddce; + font-size: 14px; +} + +.auth-card .sub-title { + margin-bottom: 18px; + text-align: center; +} + +.mode-tabs { + display: flex; + gap: 10px; + margin-bottom: 16px; +} + +.tab-btn, +button, +input, +select { + font: inherit; +} + +.tab-btn { + flex: 1; + height: 40px; + border: 1px solid #568e73; + border-radius: 10px; + color: #d9eddf; + background: transparent; + cursor: pointer; +} + +.tab-btn.active { + border-color: #d9be73; + background: #61491c; +} + +.form { + display: flex; + flex-direction: column; + gap: 12px; +} + +.field { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 14px; +} + +.field input, +.field select, +.join-line input { + height: 42px; + padding: 0 12px; + border: 1px solid rgba(208, 229, 218, 0.45); + border-radius: 8px; + color: #ebfff3; + background: rgba(0, 0, 0, 0.22); +} + +.field input::placeholder, +.join-line input::placeholder { + color: #9cbba9; +} + +.primary-btn, +.ghost-btn, +.danger-btn { + height: 42px; + border: 0; + border-radius: 8px; + font-weight: 700; + cursor: pointer; +} + +.primary-btn { + padding: 0 16px; + color: #1d311f; + background: linear-gradient(135deg, #f8df9e, #e9c76c); +} + +.ghost-btn { + padding: 0 14px; + color: #daf3e5; + border: 1px solid rgba(156, 196, 176, 0.45); + background: transparent; +} + +.danger-btn { + padding: 0 14px; + color: #fff1f1; + background: rgba(156, 44, 44, 0.8); +} + +button:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.hall-page { + display: flex; + flex-direction: column; + gap: 16px; + width: min(1200px, 100%); + margin: 0 auto; + padding: 22px; +} + +.hall-wood-bg { + position: relative; +} + +.hall-wood-bg::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + border-radius: 14px; + background: + linear-gradient(25deg, rgba(138, 99, 57, 0.24), rgba(76, 52, 31, 0.22)), + repeating-linear-gradient( + -8deg, + rgba(255, 255, 255, 0.02) 0, + rgba(255, 255, 255, 0.02) 2px, + rgba(0, 0, 0, 0.03) 2px, + rgba(0, 0, 0, 0.03) 6px + ); +} + +.hall-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding: 18px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.15); + background: linear-gradient(130deg, rgba(22, 57, 43, 0.9), rgba(10, 30, 22, 0.92)); +} + +.hall-topbar { + min-height: 88px; +} + +.brand-block { + display: flex; + align-items: center; + gap: 12px; +} + +.hall-logo { + display: grid; + place-items: center; + width: 46px; + height: 46px; + border-radius: 8px; + background: #1e6b4c; + color: #f7f0dc; + font-size: 24px; + font-weight: 700; + box-shadow: 0 8px 18px rgba(5, 17, 12, 0.4); +} + +.hall-header h1 { + font-size: 24px; +} + +.header-actions { + display: flex; + gap: 10px; +} + +.user-chip { + min-width: 180px; + padding: 10px 14px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(2, 20, 12, 0.36); +} + +.user-chip p { + font-size: 12px; + color: #acd3be; +} + +.user-chip strong { + display: inline-block; + margin-top: 4px; + font-size: 16px; +} + +.user-chip small { + display: block; + margin-top: 4px; + color: #c7e5d5; + font-size: 12px; +} + +.text-btn { + margin-left: 6px; + padding: 0; + border: 0; + background: transparent; + color: #f6de9d; + font-size: 12px; + cursor: pointer; +} + +.hall-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 16px; +} + +.hall-grid-8-4 { + grid-template-columns: 8fr 4fr; +} + +.panel { + padding: 18px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(10, 30, 22, 0.85); +} + +.panel h2 { + margin-bottom: 12px; + font-size: 20px; +} + +.create-panel { + display: flex; + flex-direction: column; + gap: 14px; +} + +.join-line { + display: grid; + grid-template-columns: 1fr auto; + gap: 10px; +} + +.room-panel { + min-height: 340px; +} + +.room-panel-header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 12px; +} + +.room-panel-header p { + color: #b5d8c4; + font-size: 13px; +} + +.icon-btn { + width: 34px; + height: 34px; + border: 1px solid rgba(176, 216, 194, 0.35); + border-radius: 8px; + color: #d3efdf; + background: rgba(17, 56, 40, 0.65); + cursor: pointer; +} + +.room-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.room-card { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12px; + padding: 12px; + border-radius: 8px; + border: 1px solid rgba(158, 198, 178, 0.28); + background: rgba(12, 45, 32, 0.65); + box-shadow: 0 10px 18px rgba(0, 0, 0, 0.2); +} + +.room-name { + font-size: 17px; + font-weight: 700; +} + +.room-tags { + margin-top: 4px; + display: flex; + gap: 8px; + color: #c4e4d3; + font-size: 13px; +} + +.room-tags span { + padding: 2px 8px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.18); +} + +.room-tags .owner-tag { + color: #f7e4b0; + border-color: rgba(247, 228, 176, 0.45); + background: rgba(30, 107, 76, 0.38); +} + +.room-id { + margin-top: 4px; + font-size: 12px; + color: #8db59f; + word-break: break-all; +} + +.room-actions-footer { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 14px; +} + +.wide-btn { + width: 100%; +} + +.side-panel { + display: flex; + flex-direction: column; + gap: 10px; +} + +.side-panel h3 { + margin: 8px 0 2px; + font-size: 16px; +} + +.hint-text { + font-size: 14px; + color: #c8e5d6; +} + +.empty-state { + display: grid; + place-items: center; + min-height: 180px; + color: #b5cfbf; + border-radius: 10px; + border: 1px dashed rgba(181, 207, 191, 0.45); +} + +.table-panel { + padding: 18px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(10, 30, 22, 0.85); +} + +.game-page { + width: 100%; + max-width: none; + min-height: 100vh; + margin: 0; + padding-top: max(12px, env(safe-area-inset-top)); + padding-right: max(12px, env(safe-area-inset-right)); + padding-bottom: max(12px, env(safe-area-inset-bottom)); + padding-left: max(12px, env(safe-area-inset-left)); +} + +.game-header { + flex: 0 0 auto; +} + +.game-table-panel { + flex: 1 1 auto; + display: flex; + flex-direction: column; + min-height: 0; +} + +.room-brief { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + padding: 8px 10px; + border-radius: 8px; + border: 1px solid rgba(194, 226, 208, 0.2); + background: rgba(7, 28, 20, 0.55); +} + +.room-brief-title { + flex: 0 0 auto; + padding: 4px 8px; + border-radius: 6px; + color: #f7e4b0; + background: rgba(30, 107, 76, 0.42); + font-size: 12px; + font-weight: 700; +} + +.room-brief-item { + min-width: 0; + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: #cee5d8; +} + +.room-brief-item em { + font-style: normal; + opacity: 0.85; +} + +.room-brief-item strong { + font-size: 13px; + color: #f4f0dc; + font-weight: 600; +} + +.room-brief-id { + flex: 1 1 auto; +} + +.room-brief-id strong { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.table-tip { + margin-top: 4px; + color: #c1dfcf; + font-size: 14px; +} + +.mahjong-table { + position: relative; + margin-top: 14px; + height: 360px; + border-radius: 16px; + border: 2px solid rgba(233, 199, 108, 0.45); + background: radial-gradient(circle at 35% 25%, #2f6d4f, #184332 58%, #113628 100%); + overflow: hidden; +} + +.game-mahjong-table { + flex: 1 1 auto; + min-height: 520px; + height: auto; +} + +.mahjong-table.active { + box-shadow: inset 0 0 0 2px rgba(250, 224, 149, 0.38); +} + +.seat { + position: absolute; + min-width: 78px; + padding: 8px 10px; + text-align: center; + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 2px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(11, 27, 20, 0.68); +} + +.seat strong { + font-size: 13px; + font-weight: 700; + color: #f4f0dc; +} + +.seat small { + font-size: 11px; + color: #cbe4d6; +} + +.seat.occupied { + border-color: rgba(244, 222, 163, 0.45); +} + +.seat.seat-me { + border-color: rgba(244, 222, 163, 0.9); + box-shadow: 0 0 0 1px rgba(244, 222, 163, 0.4); +} + +.seat.seat-turn { + background: rgba(36, 86, 64, 0.88); +} + +.turn-indicator { + display: inline-block; + margin-top: 2px; + padding: 1px 8px; + border-radius: 999px; + font-size: 10px; + color: #1c2d23; + background: #f4d685; +} + +.seat-top { + top: 16px; + left: 50%; + transform: translateX(-50%); +} + +.seat-right { + right: 16px; + top: 50%; + transform: translateY(-50%); +} + +.seat-bottom { + bottom: 16px; + left: 50%; + transform: translateX(-50%); +} + +.seat-left { + left: 16px; + top: 50%; + transform: translateY(-50%); +} + +.table-center { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: min(300px, 80%); + padding: 14px; + border-radius: 10px; + text-align: center; + border: 1px solid rgba(245, 224, 166, 0.55); + background: rgba(29, 62, 46, 0.82); +} + +.table-center p:last-child { + margin-top: 6px; + color: #f8de9d; + font-size: 13px; + word-break: break-all; +} + +.ws-panel { + margin-top: 10px; + padding: 10px; + border-radius: 8px; + border: 1px solid rgba(176, 216, 194, 0.22); + background: rgba(5, 24, 17, 0.58); +} + +.ws-panel-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.ws-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.ws-state { + padding: 2px 8px; + border-radius: 999px; + font-size: 12px; + border: 1px solid rgba(255, 255, 255, 0.18); +} + +.ws-state.is-connected { + color: #d6ffdf; + background: rgba(35, 121, 68, 0.45); +} + +.ws-state.is-connecting { + color: #fff0c2; + background: rgba(128, 99, 40, 0.45); +} + +.ws-state.is-disconnected { + color: #ffd3d3; + background: rgba(124, 45, 45, 0.45); +} + +.ws-reconnect { + height: 30px; +} + +.ws-log { + margin-top: 8px; + max-height: 140px; + overflow: auto; + padding: 8px; + border-radius: 8px; + background: rgba(0, 0, 0, 0.28); +} + +.ws-line { + font-size: 12px; + color: #cfe7da; + line-height: 1.4; +} + +.ws-line + .ws-line { + margin-top: 6px; +} + +.ws-empty { + font-size: 12px; + color: #a9c4b5; +} + +.message { + margin-top: 12px; + font-size: 14px; +} + +.message.error { + color: #ffbcbc; +} + +.message.success { + color: #caf3d6; +} + +.modal-mask { + position: fixed; + inset: 0; + z-index: 30; + display: grid; + place-items: center; + padding: 24px; + background: rgba(4, 14, 10, 0.72); + backdrop-filter: blur(2px); +} + +.modal-card { + width: min(600px, 100%); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.18); + background: linear-gradient(145deg, rgba(16, 46, 34, 0.96), rgba(7, 24, 17, 0.98)); + box-shadow: 0 20px 42px rgba(0, 0, 0, 0.5); + padding: 20px; +} + +.modal-600 { + max-width: 600px; +} + +.modal-card h2 { + margin-bottom: 14px; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 8px; +} + +.radio-group { + border: 1px solid rgba(190, 222, 204, 0.25); + border-radius: 8px; + margin: 0; + padding: 12px; +} + +.radio-group legend { + padding: 0 4px; + color: #cfe8da; +} + +.radio-group label { + margin-right: 16px; +} + +.copy-line { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + padding: 10px; + border-radius: 8px; + border: 1px solid rgba(190, 222, 204, 0.2); + background: rgba(3, 23, 15, 0.35); + margin-bottom: 10px; +} + +.copy-line span { + font-size: 13px; + color: #cfe8da; + word-break: break-all; +} + +.toast { + position: fixed; + right: 16px; + bottom: 16px; + z-index: 20; + max-width: min(560px, calc(100% - 32px)); + margin: 0; + padding: 10px 14px; + border-radius: 8px; + font-size: 14px; +} + +.toast.error { + color: #ffd7d7; + background: rgba(128, 38, 38, 0.9); +} + +.toast.success { + color: #d8ffe3; + background: rgba(28, 95, 52, 0.9); +} + +@media (max-width: 980px) { + .hall-grid { + grid-template-columns: 1fr; + } + + .room-actions-footer { + grid-template-columns: 1fr; + } + + .hall-header { + flex-direction: column; + align-items: flex-start; + } + + .header-actions { + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 640px) { + .hall-page, + .auth-page { + padding: 14px; + } + + .mahjong-table { + height: 300px; + } + + .game-page { + padding-top: max(8px, env(safe-area-inset-top)); + padding-right: max(8px, env(safe-area-inset-right)); + padding-bottom: max(8px, env(safe-area-inset-bottom)); + padding-left: max(8px, env(safe-area-inset-left)); + } + + .game-mahjong-table { + min-height: 360px; + } + + .seat { + min-width: 62px; + padding: 6px 8px; + font-size: 12px; + } + + .join-line { + grid-template-columns: 1fr; + } + + .room-brief { + flex-wrap: wrap; + gap: 8px; + } + + .room-brief-id { + flex-basis: 100%; + } +} diff --git a/src/assets/styles/reset.css b/src/assets/styles/reset.css new file mode 100644 index 0000000..e8876ad --- /dev/null +++ b/src/assets/styles/reset.css @@ -0,0 +1,41 @@ +* { + box-sizing: border-box; +} + +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#app { + margin: 0; + padding: 0; + min-height: 100%; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p, +ul, +ol { + margin: 0; + padding: 0; +} + +ul, +ol { + list-style: none; +} + +button, +input, +select, +textarea { + font: inherit; +} diff --git a/src/assets/styles/style.css b/src/assets/styles/style.css new file mode 100644 index 0000000..4118b82 --- /dev/null +++ b/src/assets/styles/style.css @@ -0,0 +1,4 @@ +@import './variables.css'; +@import './reset.css'; +@import './animation.css'; +@import './global.css'; diff --git a/src/assets/styles/variables.css b/src/assets/styles/variables.css new file mode 100644 index 0000000..c03d8ee --- /dev/null +++ b/src/assets/styles/variables.css @@ -0,0 +1,19 @@ +:root { + --font-base: 'PingFang SC', 'Microsoft YaHei', sans-serif; + + --color-bg-page: radial-gradient(circle at 12% 12%, #254935 0%, #11251c 45%, #0a1411 100%); + --color-text-main: #f7f0dc; + --color-text-sub: #c0ddce; + + --color-brand: #1e6b4c; + --color-brand-soft: #568e73; + --color-brand-gold: #f8df9e; + --color-brand-gold-2: #e9c76c; + + --radius-sm: 8px; + --radius-md: 10px; + --radius-lg: 14px; + + --shadow-card: 0 18px 40px rgba(0, 0, 0, 0.35); + --shadow-panel: 0 10px 18px rgba(0, 0, 0, 0.2); +} diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..28e7fc8 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,6 @@ +import { createApp } from 'vue' +import './assets/styles/style.css' +import App from './App.vue' +import router from './router' + +createApp(App).use(router).mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..c56e131 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,57 @@ +import { createRouter, createWebHistory } from 'vue-router' +import LoginPage from '../views/LoginPage.vue' +import RegisterPage from '../views/RegisterPage.vue' +import HallPage from '../views/HallPage.vue' +import ChengduGamePage from '../views/ChengduGamePage.vue' +import { readStoredAuth } from '../utils/auth-storage' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { + path: '/', + redirect: '/hall', + }, + { + path: '/login', + name: 'login', + component: LoginPage, + meta: { guestOnly: true }, + }, + { + path: '/register', + name: 'register', + component: RegisterPage, + meta: { guestOnly: true }, + }, + { + path: '/hall', + name: 'hall', + component: HallPage, + meta: { requiresAuth: true }, + }, + { + path: '/game/chengdu/:roomId?', + name: 'chengdu-game', + component: ChengduGamePage, + meta: { requiresAuth: true }, + }, + ], +}) + +router.beforeEach((to) => { + const auth = readStoredAuth() + const isAuthed = Boolean(auth) + + if (to.meta.requiresAuth && !isAuthed) { + return { path: '/login', query: { redirect: to.fullPath } } + } + + if (to.meta.guestOnly && isAuthed) { + return { path: '/hall' } + } + + return true +}) + +export default router diff --git a/src/types/session.ts b/src/types/session.ts new file mode 100644 index 0000000..38e861e --- /dev/null +++ b/src/types/session.ts @@ -0,0 +1,9 @@ +import type { AuthResult } from '../api/auth' + +export interface StoredAuth { + token: string + tokenType?: string + refreshToken?: string + expiresIn?: number + user?: AuthResult['user'] +} diff --git a/src/utils/auth-storage.ts b/src/utils/auth-storage.ts new file mode 100644 index 0000000..5ef09e8 --- /dev/null +++ b/src/utils/auth-storage.ts @@ -0,0 +1,43 @@ +import type { AuthResult } from '../api/auth' +import type { StoredAuth } from '../types/session' + +const AUTH_STORAGE_KEY = 'mahjong_auth' + +export function readStoredAuth(): StoredAuth | null { + const raw = localStorage.getItem(AUTH_STORAGE_KEY) + if (!raw) { + return null + } + + try { + const parsed = JSON.parse(raw) as StoredAuth + if (typeof parsed.token !== 'string' || parsed.token.length === 0) { + return null + } + + return parsed + } catch { + return null + } +} + +export function saveAuth(result: AuthResult): StoredAuth { + const value: StoredAuth = { + token: result.token, + tokenType: result.tokenType, + refreshToken: result.refreshToken, + expiresIn: result.expiresIn, + user: result.user, + } + + localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(value)) + return value +} + +export function clearAuth(): void { + localStorage.removeItem(AUTH_STORAGE_KEY) +} + +export function writeStoredAuth(value: StoredAuth): void { + localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(value)) +} diff --git a/src/views/ChengduGamePage.vue b/src/views/ChengduGamePage.vue new file mode 100644 index 0000000..4708cff --- /dev/null +++ b/src/views/ChengduGamePage.vue @@ -0,0 +1,579 @@ + + + diff --git a/src/views/HallPage.vue b/src/views/HallPage.vue new file mode 100644 index 0000000..5df7e1c --- /dev/null +++ b/src/views/HallPage.vue @@ -0,0 +1,468 @@ + + + diff --git a/src/views/LoginPage.vue b/src/views/LoginPage.vue new file mode 100644 index 0000000..fc91618 --- /dev/null +++ b/src/views/LoginPage.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/views/RegisterPage.vue b/src/views/RegisterPage.vue new file mode 100644 index 0000000..afa954f --- /dev/null +++ b/src/views/RegisterPage.vue @@ -0,0 +1,120 @@ + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..8d16e42 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..30c0c8e --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + proxy: { + '/api/v1': { + target: 'http://127.0.0.1:8080', + changeOrigin: true, + }, + '/api/v1/ws': { + target: 'ws://127.0.0.1:8080', + ws: true, + } + } + } +}) \ No newline at end of file