From e387961c590011d8ea5ca71eafb1d07ca0c48cd6 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sun, 3 Aug 2025 18:21:30 +0700 Subject: [PATCH] feat: temp --- package.json | 1 + pnpm-lock.yaml | 449 +++++++++++++++++++++++--- postcss.config.js | 2 +- src/app/api/scoring/route.js | 4 +- src/app/game/page.js | 8 +- src/app/globals.css | 15 +- src/components/features/PanoViewer.js | 107 +++++- src/utils/distance.js | 111 ++++++- tailwind.config.js | 31 -- 9 files changed, 608 insertions(+), 120 deletions(-) delete mode 100644 tailwind.config.js diff --git a/package.json b/package.json index 890f6fb..f97be25 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "tailwind-merge": "^3.3.1" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "@testing-library/jest-dom": "^6.6.4", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23b93f7..9fec1f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,9 @@ importers: specifier: ^3.3.1 version: 3.3.1 devDependencies: + '@tailwindcss/postcss': + specifier: ^4.1.11 + version: 4.1.11 '@testing-library/jest-dom': specifier: ^6.6.4 version: 6.6.4 @@ -50,10 +53,10 @@ importers: version: 10.4.21(postcss@8.5.6) eslint: specifier: ^9.0.0 - version: 9.32.0 + version: 9.32.0(jiti@2.5.1) eslint-config-next: specifier: ^15.0.0 - version: 15.4.5(eslint@9.32.0)(typescript@5.8.3) + version: 15.4.5(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) jest: specifier: ^30.0.5 version: 30.0.5(@types/node@14.18.63) @@ -75,6 +78,10 @@ packages: '@adobe/css-tools@4.4.3': resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -472,6 +479,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -712,6 +723,94 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@tailwindcss/node@4.1.11': + resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} + + '@tailwindcss/oxide-android-arm64@4.1.11': + resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.11': + resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.11': + resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.11': + resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} + '@tanstack/query-core@5.83.1': resolution: {integrity: sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==} @@ -1202,6 +1301,10 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + ci-info@4.3.0: resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} engines: {node: '>=8'} @@ -1374,6 +1477,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enhanced-resolve@5.18.2: + resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} + engines: {node: '>=10.13.0'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -2122,6 +2229,10 @@ packages: node-notifier: optional: true + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2200,6 +2311,70 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2235,6 +2410,9 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -2292,6 +2470,15 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2852,6 +3039,14 @@ packages: tailwindcss@4.1.11: resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -3069,6 +3264,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -3085,6 +3284,8 @@ snapshots: '@adobe/css-tools@4.4.3': {} + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.12 @@ -3325,9 +3526,9 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)': + '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0(jiti@2.5.1))': dependencies: - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -3477,6 +3678,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -3794,6 +3999,78 @@ snapshots: dependencies: tslib: 2.8.1 + '@tailwindcss/node@4.1.11': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.2 + jiti: 2.5.1 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.11 + + '@tailwindcss/oxide-android-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide@4.1.11': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-x64': 4.1.11 + '@tailwindcss/oxide-freebsd-x64': 4.1.11 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-x64-musl': 4.1.11 + '@tailwindcss/oxide-wasm32-wasi': 4.1.11 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + + '@tailwindcss/postcss@4.1.11': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 + postcss: 8.5.6 + tailwindcss: 4.1.11 + '@tanstack/query-core@5.83.1': {} '@tanstack/react-query@5.84.1(react@19.1.1)': @@ -3927,15 +4204,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/type-utils': 8.38.0(eslint@9.32.0)(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.32.0)(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.38.0 - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -3944,14 +4221,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3)': + '@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.38.0 '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.38.0 debug: 4.4.1 - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -3974,13 +4251,13 @@ snapshots: dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.38.0(eslint@9.32.0)(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.32.0)(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) debug: 4.4.1 - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -4004,13 +4281,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.38.0(eslint@9.32.0)(typescript@5.8.3)': + '@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.5.1)) '@typescript-eslint/scope-manager': 8.38.0 '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -4336,6 +4613,8 @@ snapshots: char-regex@1.0.2: {} + chownr@3.0.0: {} + ci-info@4.3.0: {} cjs-module-lexer@2.1.0: {} @@ -4448,8 +4727,7 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.0.4: - optional: true + detect-libc@2.0.4: {} detect-newline@3.1.0: {} @@ -4483,6 +4761,11 @@ snapshots: emoji-regex@9.2.2: {} + enhanced-resolve@5.18.2: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + entities@6.0.1: {} error-ex@1.3.2: @@ -4602,19 +4885,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@15.4.5(eslint@9.32.0)(typescript@5.8.3): + eslint-config-next@15.4.5(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3): dependencies: '@next/eslint-plugin-next': 15.4.5 '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0)(typescript@5.8.3) - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3) - eslint: 9.32.0 + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) + eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.32.0) - eslint-plugin-react: 7.37.5(eslint@9.32.0) - eslint-plugin-react-hooks: 5.2.0(eslint@9.32.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.32.0(jiti@2.5.1)) + eslint-plugin-react: 7.37.5(eslint@9.32.0(jiti@2.5.1)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.32.0(jiti@2.5.1)) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: @@ -4630,33 +4913,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.14 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3) - eslint: 9.32.0 + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) + eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4665,9 +4948,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4679,13 +4962,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.32.0): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.32.0(jiti@2.5.1)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -4695,7 +4978,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -4704,11 +4987,11 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.2.0(eslint@9.32.0): + eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.5.1)): dependencies: - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) - eslint-plugin-react@7.37.5(eslint@9.32.0): + eslint-plugin-react@7.37.5(eslint@9.32.0(jiti@2.5.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -4716,7 +4999,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.32.0 + eslint: 9.32.0(jiti@2.5.1) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -4739,9 +5022,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.32.0: + eslint@9.32.0(jiti@2.5.1): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.5.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.0 @@ -4776,6 +5059,8 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.5.1 transitivePeerDependencies: - supports-color @@ -5590,6 +5875,8 @@ snapshots: - supports-color - ts-node + jiti@2.5.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -5674,6 +5961,51 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + lines-and-columns@1.2.4: {} locate-path@5.0.0: @@ -5702,6 +6034,10 @@ snapshots: lz-string@1.5.0: {} + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + make-dir@4.0.0: dependencies: semver: 7.7.2 @@ -5797,6 +6133,12 @@ snapshots: minipass@7.1.2: {} + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + ms@2.1.3: {} murmurhash-js@1.0.0: {} @@ -6398,6 +6740,17 @@ snapshots: tailwindcss@4.1.11: {} + tapable@2.2.2: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 @@ -6664,6 +7017,8 @@ snapshots: yallist@3.1.1: {} + yallist@5.0.0: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/postcss.config.js b/postcss.config.js index 96bb01e..dc655aa 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,6 @@ module.exports = { plugins: { - tailwindcss: {}, + '@tailwindcss/postcss': {}, autoprefixer: {}, }, } \ No newline at end of file diff --git a/src/app/api/scoring/route.js b/src/app/api/scoring/route.js index 9664281..08d90c4 100644 --- a/src/app/api/scoring/route.js +++ b/src/app/api/scoring/route.js @@ -24,8 +24,8 @@ export async function POST(request) { } const sessionId = `${username}_${Date.now()}`; - const distance = calculateDistance(guessLat, guessLng, trueLat, trueLng); - const points = calculatePoints(distance); + const distance = await calculateDistance(guessLat, guessLng, trueLat, trueLng); + const points = calculatePoints(distance / 1000); // Convert to km for points calculation const sessionData = { username, diff --git a/src/app/game/page.js b/src/app/game/page.js index 25bb876..08289be 100644 --- a/src/app/game/page.js +++ b/src/app/game/page.js @@ -103,26 +103,26 @@ function GamePageContent() { setShowResultModal(true); } else { console.error('Failed to submit score'); - const fallbackDistance = calculateDistance( + const fallbackDistance = await calculateDistance( guessLocation[0], guessLocation[1], trueLocation[0], trueLocation[1] ); setDistance(fallbackDistance); - setPoints(calculatePoints(fallbackDistance)); + setPoints(calculatePoints(fallbackDistance / 1000)); // Convert to km setShowResultModal(true); } } catch (error) { console.error('Error submitting score:', error); - const fallbackDistance = calculateDistance( + const fallbackDistance = await calculateDistance( guessLocation[0], guessLocation[1], trueLocation[0], trueLocation[1] ); setDistance(fallbackDistance); - setPoints(calculatePoints(fallbackDistance)); + setPoints(calculatePoints(fallbackDistance / 1000)); // Convert to km setShowResultModal(true); } }; diff --git a/src/app/globals.css b/src/app/globals.css index 9969b61..158c5f8 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,7 +1,16 @@ @import url('https://fonts.googleapis.com/css2?family=Jersey+15:wght@400&display=swap'); -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; + +@theme { + --color-primary: #05A38C; + --color-primary-hover: #048a76; + --color-background: #1a1a1a; + --color-background-secondary: #24453D; + --color-text: #e6f4e6; + --color-text-secondary: #ccc; + --font-jersey: "Jersey 15", cursive; + --font-courier: "Courier", monospace; +} @layer base { * { diff --git a/src/components/features/PanoViewer.js b/src/components/features/PanoViewer.js index 154b968..7f881ac 100644 --- a/src/components/features/PanoViewer.js +++ b/src/components/features/PanoViewer.js @@ -7,17 +7,96 @@ import 'mapillary-js/dist/mapillary.css'; export default function PanoViewer({ imageData, isLoading, onPreciseLocationLoad }) { const panoRef = useRef(null); const viewerRef = useRef(null); + const currentImageIdRef = useRef(null); + const isInitializingRef = useRef(false); + // Clean up viewer on unmount only useEffect(() => { - if (!panoRef.current || !imageData) return; + return () => { + if (viewerRef.current) { + try { + // Only remove if navigable to avoid the error + if (viewerRef.current.isNavigable) { + viewerRef.current.remove(); + } else { + // If not navigable, just clear the reference + console.log('Viewer not navigable during cleanup, skipping remove()'); + } + } catch (error) { + // Ignore cleanup errors on unmount + } + viewerRef.current = null; + } + }; + }, []); + + // Handle image changes + useEffect(() => { + if (!panoRef.current || !imageData || isLoading) return; + + // Skip if same image + if (currentImageIdRef.current === imageData.id) return; + + // Skip if already initializing + if (isInitializingRef.current) return; + + const initOrUpdateViewer = async () => { + isInitializingRef.current = true; + currentImageIdRef.current = imageData.id; - const initViewer = async () => { try { + // If viewer exists, try to navigate to new image instead of recreating if (viewerRef.current) { - viewerRef.current.remove(); + try { + // Check if viewer is navigable before attempting moveTo + if (viewerRef.current.isNavigable) { + await viewerRef.current.moveTo(imageData.id); + isInitializingRef.current = false; + return; + } else { + console.log('Viewer is not navigable, will recreate'); + // Wait for navigation to be possible or recreate + const checkNavigable = () => { + if (viewerRef.current && viewerRef.current.isNavigable) { + viewerRef.current.moveTo(imageData.id).then(() => { + isInitializingRef.current = false; + }).catch(() => { + // If moveTo fails, recreate viewer + if (viewerRef.current) { + viewerRef.current.remove(); + viewerRef.current = null; + initOrUpdateViewer(); // Recreate + } + }); + } else { + // Viewer still not navigable, recreate immediately + if (viewerRef.current) { + viewerRef.current.remove(); + viewerRef.current = null; + } + // Continue with new viewer creation below + } + }; + + // Give it a brief moment to become navigable + setTimeout(checkNavigable, 100); + return; + } + } catch (error) { + console.log('Navigation failed, will recreate viewer:', error.message); + // Only remove if viewer is navigable or wait for it to be + if (viewerRef.current && viewerRef.current.isNavigable) { + viewerRef.current.remove(); + viewerRef.current = null; + } else { + console.log('Cannot remove non-navigable viewer, will replace'); + viewerRef.current = null; + } + } } - viewerRef.current = new Viewer({ + // Create new viewer + const newViewer = new Viewer({ accessToken: process.env.NEXT_PUBLIC_MAPILLARY_ACCESS_TOKEN || 'MLY|24113623194974280|5bf83fa202912f1cc3210b2cf968fb65', container: panoRef.current, imageId: imageData.id, @@ -41,12 +120,14 @@ export default function PanoViewer({ imageData, isLoading, onPreciseLocationLoad } }); - viewerRef.current.on('image', async (image) => { + viewerRef.current = newViewer; + + newViewer.on('image', async (image) => { console.log('MapillaryJS image loaded successfully:', image.id); try { // Get precise position from MapillaryJS - const precisePosition = await viewerRef.current.getPosition(); + const precisePosition = await newViewer.getPosition(); console.log('Precise MapillaryJS coordinates:', precisePosition); // Notify parent component of precise coordinates @@ -62,7 +143,7 @@ export default function PanoViewer({ imageData, isLoading, onPreciseLocationLoad } }); - viewerRef.current.on('error', (error) => { + newViewer.on('error', (error) => { console.error('Error loading MapillaryJS image:', error); // Fallback to regular image if (panoRef.current) { @@ -77,6 +158,7 @@ export default function PanoViewer({ imageData, isLoading, onPreciseLocationLoad onPreciseLocationLoad(imageData.lat, imageData.lng); } }); + } catch (error) { console.error('Error initializing MapillaryJS:', error); // Fallback to regular image @@ -91,17 +173,12 @@ export default function PanoViewer({ imageData, isLoading, onPreciseLocationLoad if (onPreciseLocationLoad) { onPreciseLocationLoad(imageData.lat, imageData.lng); } + } finally { + isInitializingRef.current = false; } }; - initViewer(); - - return () => { - if (viewerRef.current) { - viewerRef.current.remove(); - viewerRef.current = null; - } - }; + initOrUpdateViewer(); }, [imageData, onPreciseLocationLoad]); // Expose viewer instance for potential coordinate synchronization diff --git a/src/utils/distance.js b/src/utils/distance.js index 43e1fa2..1112601 100644 --- a/src/utils/distance.js +++ b/src/utils/distance.js @@ -1,34 +1,111 @@ /** - * Calculates the distance between two geographic coordinates using the Haversine formula + * Calculates straight-line distance between two coordinates using Haversine formula * @param {number} lat1 - Latitude of first point * @param {number} lon1 - Longitude of first point * @param {number} lat2 - Latitude of second point * @param {number} lon2 - Longitude of second point * @returns {number} Distance in meters, rounded to 1 decimal place */ -export function calculateDistance(lat1, lon1, lat2, lon2) { +function calculateHaversineDistance(lat1, lon1, lat2, lon2) { if (lat1 === lat2 && lon1 === lon2) { return 0; } - const radlat1 = Math.PI * lat1 / 180; - const radlat2 = Math.PI * lat2 / 180; - const theta = lon1 - lon2; - const radtheta = Math.PI * theta / 180; - - let dist = Math.sin(radlat1) * Math.sin(radlat2) + - Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); - - if (dist > 1) { - dist = 1; + const R = 6371000; // Earth's radius in meters + const φ1 = lat1 * Math.PI / 180; + const φ2 = lat2 * Math.PI / 180; + const Δφ = (lat2 - lat1) * Math.PI / 180; + const Δλ = (lon2 - lon1) * Math.PI / 180; + + const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + + Math.cos(φ1) * Math.cos(φ2) * + Math.sin(Δλ / 2) * Math.sin(Δλ / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + const distance = R * c; + return parseFloat(distance.toFixed(1)); +} + +/** + * Validates coordinates using OpenStreetMap Nominatim API + * @param {number} lat - Latitude + * @param {number} lon - Longitude + * @returns {Promise} True if coordinates are valid and on land + */ +async function validateCoordinatesWithOSM(lat, lon) { + try { + const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=10&addressdetails=0`; + + const response = await fetch(url, { + headers: { + 'User-Agent': 'VIGEOGUESSR/1.0 (https://vigeoguessr.com)' + } + }); + + if (!response.ok) { + return false; + } + + const data = await response.json(); + + // Check if we got a valid response (not water or empty) + return data && data.display_name && !data.error; + + } catch (error) { + console.warn('OSM validation failed:', error); + return true; // Assume valid if validation fails + } +} + +/** + * Calculates straight-line distance between two geographic coordinates + * Uses OpenStreetMap for coordinate validation and Haversine formula for distance + * @param {number} lat1 - Latitude of first point + * @param {number} lon1 - Longitude of first point + * @param {number} lat2 - Latitude of second point + * @param {number} lon2 - Longitude of second point + * @returns {Promise} Distance in meters, rounded to 1 decimal place + */ +export async function calculateDistance(lat1, lon1, lat2, lon2) { + if (lat1 === lat2 && lon1 === lon2) { + return 0; } - dist = Math.acos(dist); - dist = dist * 180 / Math.PI; - dist = dist * 60 * 1.1515; - dist = dist * 1609.34; // Convert to meters + // Validate input coordinates + if (!isValidCoordinate(lat1, lon1) || !isValidCoordinate(lat2, lon2)) { + throw new Error('Invalid coordinates provided'); + } - return parseFloat(dist.toFixed(1)); + try { + // Validate coordinates with OpenStreetMap (parallel requests) + const [valid1, valid2] = await Promise.all([ + validateCoordinatesWithOSM(lat1, lon1), + validateCoordinatesWithOSM(lat2, lon2) + ]); + + if (!valid1 || !valid2) { + console.warn('One or both coordinates may be invalid (water/empty area)'); + } + + } catch (error) { + console.warn('OSM validation failed, proceeding with calculation:', error); + } + + // Calculate straight-line distance using Haversine formula + return calculateHaversineDistance(lat1, lon1, lat2, lon2); +} + +/** + * Validates if coordinates are within valid ranges + * @param {number} lat - Latitude + * @param {number} lon - Longitude + * @returns {boolean} True if coordinates are valid + */ +function isValidCoordinate(lat, lon) { + return typeof lat === 'number' && typeof lon === 'number' && + lat >= -90 && lat <= 90 && + lon >= -180 && lon <= 180 && + !isNaN(lat) && !isNaN(lon); } /** diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index 2e423fd..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,31 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - './src/app/**/*.{js,ts,jsx,tsx,mdx}', - ], - theme: { - extend: { - colors: { - primary: { - DEFAULT: '#05A38C', - hover: '#048a76', - }, - background: { - DEFAULT: '#1a1a1a', - secondary: '#24453D', - }, - text: { - DEFAULT: '#e6f4e6', - secondary: '#ccc', - } - }, - fontFamily: { - 'jersey': ['Jersey 15', 'cursive'], - 'courier': ['Courier', 'monospace'], - }, - }, - }, - plugins: [], -} \ No newline at end of file