diff --git a/package-lock.json b/package-lock.json index 69a8638..ff8d885 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,18 @@ "dependencies": { "@supabase/ssr": "^0.10.2", "@supabase/supabase-js": "^2.103.0", - "next": "16.2.3", + "@types/d3": "^7.4.3", + "@upstash/redis": "^1.38.0", + "ai": "^6.0.191", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "d3": "^7.9.0", + "lucide-react": "^1.16.0", + "next": "^16.2.6", + "openai": "^6.39.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "recharts": "^3.8.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -27,6 +36,52 @@ "typescript": "^5" } }, + "node_modules/@ai-sdk/gateway": { + "version": "3.0.120", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.120.tgz", + "integrity": "sha512-MYKAeD2q7/sa1ZdqtL2tw0Me0B8Tok6Q/fhkJDhJl39dG8u+VBlWO9yk9lcdm784bM418o1EKObo4aOxs6+18Q==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27", + "@vercel/oidc": "3.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.10.tgz", + "integrity": "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", + "integrity": "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -1039,9 +1094,9 @@ } }, "node_modules/@next/env": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.3.tgz", - "integrity": "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", + "integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1055,9 +1110,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.3.tgz", - "integrity": "sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", + "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", "cpu": [ "arm64" ], @@ -1071,9 +1126,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.3.tgz", - "integrity": "sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", + "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", "cpu": [ "x64" ], @@ -1087,9 +1142,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.3.tgz", - "integrity": "sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", + "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", "cpu": [ "arm64" ], @@ -1103,9 +1158,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.3.tgz", - "integrity": "sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", + "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", "cpu": [ "arm64" ], @@ -1119,9 +1174,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.3.tgz", - "integrity": "sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz", + "integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==", "cpu": [ "x64" ], @@ -1135,9 +1190,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.3.tgz", - "integrity": "sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz", + "integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==", "cpu": [ "x64" ], @@ -1151,9 +1206,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.3.tgz", - "integrity": "sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", + "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", "cpu": [ "arm64" ], @@ -1167,9 +1222,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.3.tgz", - "integrity": "sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", + "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", "cpu": [ "x64" ], @@ -1230,6 +1285,51 @@ "node": ">=12.4.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", + "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1237,6 +1337,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@supabase/auth-js": { "version": "2.103.0", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.103.0.tgz", @@ -1626,6 +1738,259 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1633,6 +1998,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1660,7 +2031,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1676,6 +2047,12 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -1884,9 +2261,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -2249,6 +2626,24 @@ "win32" ] }, + "node_modules/@upstash/redis": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.38.0.tgz", + "integrity": "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/@vercel/oidc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", + "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2272,6 +2667,24 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ai": { + "version": "6.0.191", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.191.tgz", + "integrity": "sha512-zAxvjKebQE7YkSyyNIl0OM7i6/zygnKeF+yNUjD4nWOelYrG+LpDd6RnH6mjySI4zUpZ7o4wbnmAy8jc6u98vQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "3.0.120", + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27", + "@opentelemetry/api": "^1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -2721,148 +3134,579 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=12" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", "dependencies": { - "restore-cursor": "^5.0.0" + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" + "d3-path": "^3.1.0" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "d3-array": "2 - 3" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, "engines": { - "node": ">=20" + "node": ">=12" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, "engines": { - "node": ">=18" + "node": ">=12" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -2942,6 +3786,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2985,6 +3835,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3242,6 +4101,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", + "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3675,9 +4544,17 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, "license": "MIT" }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4162,6 +5039,18 @@ "node": ">=20.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4172,6 +5061,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4214,6 +5113,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4727,6 +5635,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5226,6 +6140,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", + "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -5314,9 +6237,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -5355,12 +6278,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz", - "integrity": "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", + "integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==", "license": "MIT", "dependencies": { - "@next/env": "16.2.3", + "@next/env": "16.2.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -5374,14 +6297,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.3", - "@next/swc-darwin-x64": "16.2.3", - "@next/swc-linux-arm64-gnu": "16.2.3", - "@next/swc-linux-arm64-musl": "16.2.3", - "@next/swc-linux-x64-gnu": "16.2.3", - "@next/swc-linux-x64-musl": "16.2.3", - "@next/swc-win32-arm64-msvc": "16.2.3", - "@next/swc-win32-x64-msvc": "16.2.3", + "@next/swc-darwin-arm64": "16.2.6", + "@next/swc-darwin-x64": "16.2.6", + "@next/swc-linux-arm64-gnu": "16.2.6", + "@next/swc-linux-arm64-musl": "16.2.6", + "@next/swc-linux-x64-gnu": "16.2.6", + "@next/swc-linux-x64-musl": "16.2.6", + "@next/swc-win32-arm64-msvc": "16.2.6", + "@next/swc-win32-x64-msvc": "16.2.6", "sharp": "^0.34.5" }, "peerDependencies": { @@ -5600,6 +6523,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "6.39.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.39.0.tgz", + "integrity": "sha512-O61LIsimY3acVabwvomwFhwrnN36yvHY2quIfy9keEcFytGgWeV35yLHQ6NVMLSBxRpHmcg2yuhCnlu2HT4pLQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5738,9 +6682,9 @@ } }, "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -5758,7 +6702,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5844,9 +6788,76 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/recharts": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", + "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5891,6 +6902,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "2.0.0-next.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", @@ -5970,6 +6987,12 @@ "dev": true, "license": "MIT" }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5994,6 +7017,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -6049,6 +7078,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -6593,6 +7628,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", @@ -6857,6 +7898,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -6939,6 +7986,37 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7111,9 +8189,9 @@ } }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -7171,7 +8249,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index d981c2e..fd7ca96 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,18 @@ "dependencies": { "@supabase/ssr": "^0.10.2", "@supabase/supabase-js": "^2.103.0", - "next": "16.2.3", + "@types/d3": "^7.4.3", + "@upstash/redis": "^1.38.0", + "ai": "^6.0.191", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "d3": "^7.9.0", + "lucide-react": "^1.16.0", + "next": "^16.2.6", + "openai": "^6.39.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "recharts": "^3.8.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx new file mode 100644 index 0000000..1db7277 --- /dev/null +++ b/src/app/dashboard/layout.tsx @@ -0,0 +1,10 @@ +/** + * Dashboard Layout - Placeholder for Phase 2 + */ +export default function DashboardLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return <>{children}; +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..42c20f3 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,6 @@ +/** + * Placeholder - will be replaced in Phase 2 + */ +export default function DashboardPage() { + return
Placeholder
; +} diff --git a/src/app/globals.css b/src/app/globals.css index a2dc41e..a5ab127 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,26 +1,136 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; -} + /* Primary Colors */ + --primary: #00ff87; + --primary-dark: #00d966; + --primary-light: #33ff99; + + /* Background Shades */ + --background: #0d0d0d; + --background-secondary: #1a1a1a; + --background-tertiary: #262626; + + /* Foreground / Text */ + --foreground: #f5f5f5; + --foreground-secondary: #b0b0b0; + --foreground-tertiary: #808080; + + /* Accent Colors */ + --accent: #00ff87; + --accent-red: #ff4444; + --accent-yellow: #ffd700; + --accent-green: #00ff87; + + /* Borders & Dividers */ + --border: #333333; + --border-light: #404040; -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); + /* Semantic Colors */ + --success: #00ff87; + --warning: #ffd700; + --error: #ff4444; + --info: #3d00ff; + + /* Fonts */ --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); } -@media (prefers-color-scheme: dark) { +@media (prefers-color-scheme: light) { :root { - --background: #0a0a0a; - --foreground: #ededed; + --background: #ffffff; + --background-secondary: #f9f9f9; + --background-tertiary: #f0f0f0; + --foreground: #0d0d0d; + --foreground-secondary: #4d4d4d; + --foreground-tertiary: #7f7f7f; + --border: #e0e0e0; + --border-light: #d0d0d0; } } +/* Dark mode is default */ +html { + color-scheme: dark; +} + body { - background: var(--background); + background-color: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: var(--font-sans); + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Scrollbar Styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--background-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--border-light); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--foreground-secondary); +} + +/* Selection */ +::selection { + background-color: var(--primary); + color: var(--background); +} + +/* Focus visible for accessibility */ +:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* Smooth scrolling */ +html { + scroll-behavior: smooth; +} + +/* Link defaults */ +a { + color: var(--primary); + text-decoration: none; + transition: color 200ms ease; +} + +a:hover { + color: var(--primary-light); +} + +/* Utility classes for common patterns */ +.live-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.live-dot { + display: inline-block; + width: 8px; + height: 8px; + background-color: var(--accent-green); + border-radius: 50%; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 976eb90..c8a084d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,8 +13,19 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Swale — Football Intelligence Dashboard", + description: + "Real-time football scores, player analytics, and AI-powered insights across 50+ leagues and international tournaments.", + viewport: "width=device-width, initial-scale=1, maximum-scale=5", + keywords: [ + "football", + "soccer", + "live scores", + "player stats", + "world cup", + "analytics", + "xG", + ], }; export default function RootLayout({ @@ -27,7 +38,9 @@ export default function RootLayout({ lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} > - {children} + + {children} + ); } diff --git a/src/lib/api-football/client.ts b/src/lib/api-football/client.ts new file mode 100644 index 0000000..2d5b8a4 --- /dev/null +++ b/src/lib/api-football/client.ts @@ -0,0 +1,109 @@ +/** + * API-Football Client + * Typed fetcher for API-Football (api-football.com) endpoints + * Used for live scores, player stats, and match data + */ + +import { Match, MatchStatus, Team, Player, Competition, CompetitionCode } from "@/types"; + +const API_FOOTBALL_BASE = "https://api.api-football.com/v3"; +const API_FOOTBALL_KEY = process.env.API_FOOTBALL_KEY; + +if (!API_FOOTBALL_KEY) { + console.warn("API_FOOTBALL_KEY is not set in environment variables"); +} + +interface APIFootballResponse { + get: string; + parameters: Record; + errors: Record; + results: number; + paging: { + current: number; + total: number; + }; + response: T[]; +} + +class APIFootballClient { + private baseUrl = API_FOOTBALL_BASE; + private apiKey = API_FOOTBALL_KEY; + + private async request(endpoint: string, params?: Record): Promise { + if (!this.apiKey) { + console.error("API_FOOTBALL_KEY is required"); + return []; + } + + const url = new URL(`${this.baseUrl}${endpoint}`); + if (params) { + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + url.searchParams.append(key, String(value)); + } + }); + } + + try { + const response = await fetch(url.toString(), { + headers: { + "x-apisports-key": this.apiKey, + }, + }); + + if (!response.ok) { + throw new Error(`API-Football error: ${response.statusText}`); + } + + const data = (await response.json()) as APIFootballResponse; + return data.response; + } catch (error) { + console.error("API-Football request failed:", error); + throw error; + } + } + + /** + * Get fixtures (matches) for a specific date or date range + */ + async getFixtures(params?: { + date?: string; // YYYY-MM-DD + dateFrom?: string; + dateTo?: string; + league?: number; + season?: number; + team?: number; + status?: string; // LIVE, FT, etc. + limit?: number; + }): Promise { + // This is a mock implementation since we don't have real API key + // In production, this would call the actual API-Football endpoint + return []; + } + + /** + * Get player information with stats + */ + async getPlayer(playerId: number): Promise { + // Mock implementation + return null; + } + + /** + * Get standings for a league + */ + async getStandings(leagueId: number, season: number) { + // Mock implementation + return []; + } + + /** + * Get live matches across all leagues + */ + async getLiveMatches(): Promise { + // Mock implementation + return []; + } +} + +export const apiFootballClient = new APIFootballClient(); diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..fa07a77 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,256 @@ +/** + * Global Constants and Configuration + */ + +import { CompetitionCode, Competition } from "@/types"; + +// ============================================================================ +// COLORS & THEME +// ============================================================================ + +export const THEME = { + primary: "#00FF87", // Electric green + primaryDark: "#00D966", + primaryLight: "#33FF99", + background: "#0d0d0d", + backgroundSecondary: "#1a1a1a", + backgroundTertiary: "#262626", + foreground: "#f5f5f5", + foregroundSecondary: "#b0b0b0", + accent: "#00FF87", + accentRed: "#FF4444", + accentYellow: "#FFD700", + accentGreen: "#00FF87", + borderColor: "#333333", +} as const; + +// ============================================================================ +// COMPETITIONS +// ============================================================================ + +export const COMPETITIONS: Record = { + [CompetitionCode.PREMIER_LEAGUE]: { + code: CompetitionCode.PREMIER_LEAGUE, + name: "Premier League", + country: "England", + type: "league", + color: "#3D00FF", + }, + [CompetitionCode.LA_LIGA]: { + code: CompetitionCode.LA_LIGA, + name: "La Liga", + country: "Spain", + type: "league", + color: "#FFC400", + }, + [CompetitionCode.SERIE_A]: { + code: CompetitionCode.SERIE_A, + name: "Serie A", + country: "Italy", + type: "league", + color: "#0066CC", + }, + [CompetitionCode.BUNDESLIGA]: { + code: CompetitionCode.BUNDESLIGA, + name: "Bundesliga", + country: "Germany", + type: "league", + color: "#FF0000", + }, + [CompetitionCode.LIGUE_1]: { + code: CompetitionCode.LIGUE_1, + name: "Ligue 1", + country: "France", + type: "league", + color: "#0066FF", + }, + [CompetitionCode.WORLD_CUP]: { + code: CompetitionCode.WORLD_CUP, + name: "FIFA World Cup", + type: "international", + color: "#FFD700", + }, + [CompetitionCode.CHAMPIONS_LEAGUE]: { + code: CompetitionCode.CHAMPIONS_LEAGUE, + name: "UEFA Champions League", + type: "cup", + color: "#0066FF", + }, + [CompetitionCode.EUROPA_LEAGUE]: { + code: CompetitionCode.EUROPA_LEAGUE, + name: "UEFA Europa League", + type: "cup", + color: "#FF6600", + }, +}; + +// ============================================================================ +// NAVIGATION +// ============================================================================ + +export const MAIN_COMPETITIONS = [ + CompetitionCode.PREMIER_LEAGUE, + CompetitionCode.LA_LIGA, + CompetitionCode.SERIE_A, + CompetitionCode.BUNDESLIGA, + CompetitionCode.LIGUE_1, +]; + +export const NAV_ITEMS = [ + { label: "Live Now", href: "/", icon: "live" }, + { label: "Premier League", href: "/?competition=PL", icon: "trophy" }, + { label: "La Liga", href: "/?competition=LA", icon: "trophy" }, + { label: "Serie A", href: "/?competition=SA", icon: "trophy" }, + { label: "Bundesliga", href: "/?competition=BL", icon: "trophy" }, + { label: "Ligue 1", href: "/?competition=L1", icon: "trophy" }, + { label: "World Cup", href: "/world-cup", icon: "globe" }, + { label: "Players", href: "/players", icon: "user" }, + { label: "News", href: "/news", icon: "newspaper" }, +]; + +// ============================================================================ +// MATCH STATUS DISPLAY +// ============================================================================ + +export const MATCH_STATUS_DISPLAY = { + scheduled: "Scheduled", + live: "Live", + halftime: "Half-time", + finished: "Full Time", + postponed: "Postponed", + cancelled: "Cancelled", +} as const; + +// ============================================================================ +// PLAYER POSITIONS +// ============================================================================ + +export const PLAYER_POSITIONS = { + GOALKEEPER: "Goalkeeper", + DEFENDER: "Defender", + MIDFIELDER: "Midfielder", + FORWARD: "Forward", +} as const; + +export const POSITION_COLORS = { + [PLAYER_POSITIONS.GOALKEEPER]: "#FFD700", + [PLAYER_POSITIONS.DEFENDER]: "#3D00FF", + [PLAYER_POSITIONS.MIDFIELDER]: "#00FF87", + [PLAYER_POSITIONS.FORWARD]: "#FF4444", +} as const; + +// ============================================================================ +// RATING THRESHOLDS +// ============================================================================ + +export const RATING_THRESHOLDS = { + EXCELLENT: 7.5, + GOOD: 6.5, + FAIR: 5.5, + POOR: 4.5, +} as const; + +export const getRatingColor = (rating: number): string => { + if (rating >= RATING_THRESHOLDS.EXCELLENT) return "#00FF87"; // Green + if (rating >= RATING_THRESHOLDS.GOOD) return "#90EE90"; // Light green + if (rating >= RATING_THRESHOLDS.FAIR) return "#FFD700"; // Yellow + if (rating >= RATING_THRESHOLDS.POOR) return "#FF8C00"; // Orange + return "#FF4444"; // Red +}; + +// ============================================================================ +// PAGINATION +// ============================================================================ + +export const DEFAULT_PAGE_SIZE = 20; +export const MAX_PAGE_SIZE = 100; + +// ============================================================================ +// CACHE TTL (seconds) +// ============================================================================ + +export const CACHE_TTL = { + LIVE_SCORES: 30, // 30 seconds + MATCH_DETAIL: 60, // 1 minute + PLAYER_STATS: 300, // 5 minutes + WORLD_CUP_HISTORY: 86400, // 24 hours + STANDINGS: 600, // 10 minutes + NEWS: 1800, // 30 minutes +} as const; + +// ============================================================================ +// API ENDPOINTS +// ============================================================================ + +export const API_ENDPOINTS = { + SCORES: "/api/scores", + PLAYER: "/api/player", + WORLD_CUP: "/api/worldcup", + AI_MATCH_PREVIEW: "/api/ai/match-preview", + AI_PLAYER_SUMMARY: "/api/ai/player-summary", +} as const; + +// ============================================================================ +// DATE FORMATS +// ============================================================================ + +export const DATE_FORMATS = { + SHORT: "MMM d", + MEDIUM: "MMM d, yyyy", + LONG: "EEEE, MMMM d, yyyy", + TIME: "HH:mm", + DATETIME: "MMM d, yyyy HH:mm", +} as const; + +// ============================================================================ +// RADAR CHART STATS +// ============================================================================ + +export const RADAR_STATS = [ + { label: "xG", maxValue: 5 }, + { label: "xA", maxValue: 3 }, + { label: "Progressive Carries", maxValue: 10 }, + { label: "Pressures", maxValue: 30 }, + { label: "Dribbles", maxValue: 8 }, + { label: "Aerial Duels Won", maxValue: 10 }, +] as const; + +// ============================================================================ +// WORLD CUP GROUPS +// ============================================================================ + +export const WORLD_CUP_GROUPS = ["A", "B", "C", "D", "E", "F", "G", "H"] as const; + +// ============================================================================ +// FORMATION POSITIONS +// ============================================================================ + +export const FORMATION_COORDINATES: Record> = { + "4-3-3": { + GK: [50, 5], + CB: [20, 25], + CB_2: [80, 25], + LB: [10, 45], + RB: [90, 45], + CM: [30, 65], + CM_2: [50, 70], + CM_3: [70, 65], + LW: [20, 85], + ST: [50, 95], + RW: [80, 85], + }, + "5-4-1": { + GK: [50, 5], + LWB: [10, 30], + CB: [30, 25], + CB_2: [50, 20], + CB_3: [70, 25], + RWB: [90, 30], + LM: [25, 55], + CM: [40, 65], + CM_2: [60, 65], + RM: [75, 55], + ST: [50, 85], + }, + // Add more formations as needed +} as const; diff --git a/src/lib/openai/client.ts b/src/lib/openai/client.ts new file mode 100644 index 0000000..5b9039c --- /dev/null +++ b/src/lib/openai/client.ts @@ -0,0 +1,20 @@ +/** + * OpenAI Client Initialization + * Provides GPT-4o mini for AI-powered insights + */ + +import { OpenAI } from "openai"; + +const openaiApiKey = process.env.OPENAI_API_KEY; + +if (!openaiApiKey) { + console.warn("OPENAI_API_KEY is not set in environment variables"); +} + +export const openai = new OpenAI({ + apiKey: openaiApiKey, +}); + +export const MODEL = "gpt-4o-mini"; +export const MAX_TOKENS = 500; +export const TEMPERATURE = 0.7; diff --git a/src/lib/openai/prompts.ts b/src/lib/openai/prompts.ts new file mode 100644 index 0000000..6dfbbdd --- /dev/null +++ b/src/lib/openai/prompts.ts @@ -0,0 +1,102 @@ +/** + * OpenAI Prompt Templates + * Pre-crafted prompts for match previews and player summaries + */ + +import { PlayerFormMatch } from "@/types"; + +export const MATCH_PREVIEW_SYSTEM_PROMPT = `You are an expert football/soccer analyst with deep knowledge of player tactics, form, and match dynamics. +Provide concise, insightful match previews that: +- Focus on tactical matchups and key players +- Reference recent form and injury concerns +- Highlight historical head-to-head patterns +- Give a clear prediction with reasoning +Keep your response to 2-3 short paragraphs, highly technical but accessible.`; + +export function generateMatchPreviewPrompt( + homeTeam: string, + awayTeam: string, + competition: string, + recentForm?: { + homeTeamForm: PlayerFormMatch[]; + awayTeamForm: PlayerFormMatch[]; + } +): string { + const homeFormText = + recentForm?.homeTeamForm + ?.slice(0, 3) + ?.map((m) => `vs ${m.opponent}: ${m.rating}/10`) + ?.join(", ") || "No recent data"; + + const awayFormText = + recentForm?.awayTeamForm + ?.slice(0, 3) + ?.map((m) => `vs ${m.opponent}: ${m.rating}/10`) + ?.join(", ") || "No recent data"; + + return `Provide a match preview for ${homeTeam} vs ${awayTeam} in the ${competition}. + +Recent form: +- ${homeTeam}: ${homeFormText} +- ${awayTeam}: ${awayFormText} + +Focus on: tactical setup, key player matchups, form analysis, and a match prediction with odds assessment.`; +} + +export const PLAYER_SUMMARY_SYSTEM_PROMPT = `You are a football analytics expert specializing in player evaluation and performance trends. +Provide insightful player summaries that: +- Analyze current form and recent performance +- Compare stats to position peers and league averages +- Identify key strengths and areas for improvement +- Give an overall assessment and market outlook +Keep your response to 2-3 short paragraphs, data-driven but engaging.`; + +export function generatePlayerSummaryPrompt( + playerName: string, + position: string, + club: string, + season: number, + stats?: { + appearances: number; + goals: number; + assists: number; + xG: number; + xA: number; + rating: number; + } +): string { + const statsText = + stats && + `${stats.appearances} apps, ${stats.goals} goals, ${stats.assists} assists, ${stats.xG.toFixed(1)} xG, ${stats.xA.toFixed(1)} xA, avg rating ${stats.rating.toFixed(1)}/10`; + + return `Provide a player summary for ${playerName} (${position}, ${club}). + +Season ${season} Statistics: +${statsText || "Stats not available"} + +Analyze: recent form, comparison to peers, key strengths, weaknesses, and market trajectory.`; +} + +export const TREND_NARRATIVE_SYSTEM_PROMPT = `You are a football journalist with expertise in trends and pattern analysis. +Provide engaging narratives that: +- Identify emerging trends in the data +- Connect player/team performance to broader patterns +- Use engaging, accessible language +- Provide actionable insights +Keep your response to 1-2 paragraphs, engaging and insightful.`; + +export function generateTrendNarrativePrompt( + topic: string, + data: Record +): string { + const dataText = Object.entries(data) + .map(([key, value]) => `${key}: ${value}`) + .join("\n"); + + return `Write a brief narrative about the following trend in football: ${topic} + +Data: +${dataText} + +Provide insights about what this trend means for the sport and key players affected.`; +} diff --git a/src/lib/redis/cache.ts b/src/lib/redis/cache.ts new file mode 100644 index 0000000..15c8a43 --- /dev/null +++ b/src/lib/redis/cache.ts @@ -0,0 +1,122 @@ +/** + * Upstash Redis Cache Utilities + * Provides simple get/set operations with TTL support + */ + +import { Redis } from "@upstash/redis"; + +const redis = new Redis({ + url: process.env.UPSTASH_REDIS_REST_URL, + token: process.env.UPSTASH_REDIS_REST_TOKEN, +}); + +export interface CacheOptions { + ttl?: number; // seconds + namespace?: string; +} + +class CacheManager { + private namespace = "swale:"; + + /** + * Get value from cache + */ + async get(key: string, options?: CacheOptions): Promise { + try { + const fullKey = this.buildKey(key, options?.namespace); + const value = await redis.get(fullKey); + return value as T | null; + } catch (error) { + console.error("Cache GET error:", error); + return null; + } + } + + /** + * Set value in cache with optional TTL + */ + async set( + key: string, + value: T, + options?: CacheOptions + ): Promise { + try { + const fullKey = this.buildKey(key, options?.namespace); + if (options?.ttl) { + await redis.setex(fullKey, options.ttl, value); + } else { + await redis.set(fullKey, value); + } + return true; + } catch (error) { + console.error("Cache SET error:", error); + return false; + } + } + + /** + * Delete value from cache + */ + async delete(key: string, namespace?: string): Promise { + try { + const fullKey = this.buildKey(key, namespace); + await redis.del(fullKey); + return true; + } catch (error) { + console.error("Cache DELETE error:", error); + return false; + } + } + + /** + * Clear all cache with optional namespace filter + */ + async clear(namespace?: string): Promise { + try { + const pattern = namespace ? `${namespace}:*` : `${this.namespace}*`; + // Note: KEYS pattern matching is not ideal for Redis. + // In production, consider using SCAN for large datasets + const keys = await redis.keys(pattern); + if (keys.length > 0) { + await redis.del(...keys); + } + return true; + } catch (error) { + console.error("Cache CLEAR error:", error); + return false; + } + } + + /** + * Get or set pattern: try cache, if miss call function and cache result + */ + async getOrSet( + key: string, + fn: () => Promise, + options?: CacheOptions + ): Promise { + // Try to get from cache + const cached = await this.get(key, options); + if (cached !== null) { + return cached; + } + + // Cache miss: execute function and cache result + const result = await fn(); + await this.set(key, result, options); + return result; + } + + /** + * Build fully qualified cache key + */ + private buildKey(key: string, namespace?: string): string { + const ns = namespace || this.namespace; + return `${ns}${key}`; + } +} + +export const cacheManager = new CacheManager(); + +// Re-export for convenience +export { redis }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..6be4d20 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,339 @@ +/** + * Utility functions for formatting, calculations, and common operations + */ + +import { Match, MatchStatus, PlayerFormMatch } from "@/types"; +import { MATCH_STATUS_DISPLAY, getRatingColor, RATING_THRESHOLDS } from "./constants"; + +// ============================================================================ +// DATE & TIME UTILITIES +// ============================================================================ + +export function formatTime(date: Date, format: "HH:mm" | "MMM d" = "HH:mm"): string { + const d = new Date(date); + const hours = String(d.getHours()).padStart(2, "0"); + const minutes = String(d.getMinutes()).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + const month = d.toLocaleString("en-US", { month: "short" }); + + if (format === "HH:mm") return `${hours}:${minutes}`; + if (format === "MMM d") return `${month} ${day}`; + return d.toLocaleDateString(); +} + +export function isToday(date: Date): boolean { + const today = new Date(); + const d = new Date(date); + return ( + today.getFullYear() === d.getFullYear() && + today.getMonth() === d.getMonth() && + today.getDate() === d.getDate() + ); +} + +export function isSoon(date: Date, minutesAhead: number = 30): boolean { + const now = new Date(); + const diff = date.getTime() - now.getTime(); + return diff > 0 && diff < minutesAhead * 60 * 1000; +} + +export function formatMatchTime(match: Match): string { + if (match.status === MatchStatus.LIVE && match.minute) { + return `${match.minute}'`; + } + if (match.status === MatchStatus.FINISHED) { + return "FT"; + } + if (match.status === MatchStatus.HALFTIME) { + return "HT"; + } + return formatTime(match.kickoffTime, "HH:mm"); +} + +// ============================================================================ +// NUMBER FORMATTING +// ============================================================================ + +export function formatNumber(num: number, decimals: number = 1): string { + if (num >= 1_000_000) { + return `${(num / 1_000_000).toFixed(decimals)}M`; + } + if (num >= 1_000) { + return `${(num / 1_000).toFixed(decimals)}K`; + } + return num.toString(); +} + +export function formatCurrency(amount: number, currency: string = "EUR"): string { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency, + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(amount); +} + +export function formatPercentage(value: number, decimals: number = 1): string { + return `${(value * 100).toFixed(decimals)}%`; +} + +// ============================================================================ +// MATCH STATUS & BADGE UTILITIES +// ============================================================================ + +export function getMatchStatusDisplay(status: MatchStatus): string { + return MATCH_STATUS_DISPLAY[status]; +} + +export function isMatchLive(match: Match): boolean { + return match.status === MatchStatus.LIVE; +} + +export function isMatchFinished(match: Match): boolean { + return match.status === MatchStatus.FINISHED; +} + +export function isMatchScheduled(match: Match): boolean { + return match.status === MatchStatus.SCHEDULED; +} + +// ============================================================================ +// PLAYER RATING & PERFORMANCE +// ============================================================================ + +export function getRatingBadgeColor(rating: number): string { + return getRatingColor(rating); +} + +export function getRatingLabel(rating: number): string { + if (rating >= RATING_THRESHOLDS.EXCELLENT) return "Excellent"; + if (rating >= RATING_THRESHOLDS.GOOD) return "Good"; + if (rating >= RATING_THRESHOLDS.FAIR) return "Fair"; + if (rating >= RATING_THRESHOLDS.POOR) return "Poor"; + return "Very Poor"; +} + +export function calculateFormTrend(form: PlayerFormMatch[]): "up" | "down" | "stable" { + if (form.length < 2) return "stable"; + + const recent = form.slice(0, 3); + const earlier = form.slice(3, 6); + + const recentAvg = recent.reduce((sum, m) => sum + m.rating, 0) / recent.length; + const earlierAvg = earlier.reduce((sum, m) => sum + m.rating, 0) / earlier.length; + + const diff = recentAvg - earlierAvg; + if (Math.abs(diff) < 0.3) return "stable"; + return diff > 0 ? "up" : "down"; +} + +export function getFormTrendEmoji(trend: "up" | "down" | "stable"): string { + if (trend === "up") return "📈"; + if (trend === "down") return "📉"; + return "➡️"; +} + +// ============================================================================ +// SCORE & GOAL DIFFERENCE +// ============================================================================ + +export function calculateGoalDifference( + goalsFor: number, + goalsAgainst: number +): number { + return goalsFor - goalsAgainst; +} + +export function getMatchWinner( + homeScore: number, + awayScore: number +): "home" | "away" | "draw" { + if (homeScore > awayScore) return "home"; + if (awayScore > homeScore) return "away"; + return "draw"; +} + +export function getPointsFromMatch( + homeScore: number, + awayScore: number +): { home: number; away: number } { + const winner = getMatchWinner(homeScore, awayScore); + if (winner === "home") return { home: 3, away: 0 }; + if (winner === "away") return { home: 0, away: 3 }; + return { home: 1, away: 1 }; +} + +// ============================================================================ +// POSITION & FORMATION +// ============================================================================ + +export function getPositionCategory(position: string): "GK" | "DEF" | "MID" | "FWD" { + const pos = position.toUpperCase(); + if (pos.includes("GOALKEEPER") || pos === "GK") return "GK"; + if (pos.includes("DEFENDER") || pos.includes("BACK")) return "DEF"; + if (pos.includes("MIDFIELDER") || pos === "MID") return "MID"; + if (pos.includes("FORWARD") || pos === "FWD" || pos === "ST") return "FWD"; + return "MID"; // default +} + +export function getPositionShorthand(position: string): string { + const pos = position.toUpperCase(); + if (pos.includes("GOALKEEPER")) return "GK"; + if (pos.includes("LEFT BACK")) return "LB"; + if (pos.includes("RIGHT BACK")) return "RB"; + if (pos.includes("CENTER BACK")) return "CB"; + if (pos.includes("LEFT WING")) return "LW"; + if (pos.includes("RIGHT WING")) return "RW"; + if (pos.includes("MIDFIELDER")) return "MID"; + if (pos.includes("STRIKER")) return "ST"; + if (pos.includes("FORWARD")) return "FW"; + return pos.substring(0, 3).toUpperCase(); +} + +// ============================================================================ +// STRING & CLASS UTILITIES +// ============================================================================ + +export function cn(...classes: (string | undefined | false | null)[]): string { + return classes.filter(Boolean).join(" "); +} + +export function truncateString(str: string, maxLength: number): string { + if (str.length <= maxLength) return str; + return `${str.substring(0, maxLength)}...`; +} + +export function capitalize(str: string): string { + if (str.length === 0) return str; + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); +} + +// ============================================================================ +// SORTING & FILTERING +// ============================================================================ + +export function sortMatches( + matches: Match[], + by: "date" | "competition" = "date" +): Match[] { + return [...matches].sort((a, b) => { + if (by === "date") { + return a.kickoffTime.getTime() - b.kickoffTime.getTime(); + } + return a.competition.name.localeCompare(b.competition.name); + }); +} + +export function groupMatchesByCompetition( + matches: Match[] +): Record { + return matches.reduce( + (acc, match) => { + const key = match.competition.code; + if (!acc[key]) acc[key] = []; + acc[key].push(match); + return acc; + }, + {} as Record + ); +} + +export function groupMatchesByDate(matches: Match[]): Record { + return matches.reduce( + (acc, match) => { + const key = formatTime(match.kickoffTime, "MMM d"); + if (!acc[key]) acc[key] = []; + acc[key].push(match); + return acc; + }, + {} as Record + ); +} + +// ============================================================================ +// VALIDATION +// ============================================================================ + +export function isValidEmail(email: string): boolean { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +} + +export function isValidURL(url: string): boolean { + try { + new URL(url); + return true; + } catch { + return false; + } +} + +// ============================================================================ +// API HELPERS +// ============================================================================ + +export async function fetchWithTimeout( + url: string, + options: RequestInit = {}, + timeoutMs: number = 10000 +): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + + try { + const response = await fetch(url, { + ...options, + signal: controller.signal, + }); + clearTimeout(timeout); + return response; + } catch (error) { + clearTimeout(timeout); + throw error; + } +} + +// ============================================================================ +// CACHE KEY GENERATORS +// ============================================================================ + +export function getCacheKey( + resource: string, + ...params: (string | number | undefined)[] +): string { + const filteredParams = params.filter(Boolean); + return [resource, ...filteredParams].join(":"); +} + +export function getScoresCacheKey(date?: string, competition?: string): string { + return getCacheKey("scores", date, competition); +} + +export function getPlayerCacheKey(playerId: string): string { + return getCacheKey("player", playerId); +} + +export function getWorldCupCacheKey(year: number): string { + return getCacheKey("worldcup", year); +} + +// ============================================================================ +// DISTANCE & COORDINATES +// ============================================================================ + +export function getDistance( + x1: number, + y1: number, + x2: number, + y2: number +): number { + return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); +} + +export function calculatePercentPosition( + value: number, + min: number, + max: number +): number { + return ((value - min) / (max - min)) * 100; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..fa00d12 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,362 @@ +/** + * Core Football Intelligence Types + * Shared across all Swale components and API routes + */ + +// ============================================================================ +// MATCH TYPES +// ============================================================================ + +export enum MatchStatus { + SCHEDULED = "scheduled", + LIVE = "live", + HALFTIME = "halftime", + FINISHED = "finished", + POSTPONED = "postponed", + CANCELLED = "cancelled", +} + +export interface Team { + id: string; + name: string; + shortName: string; + code?: string; + logo?: string; + flag?: string; +} + +export interface MatchEvent { + id: string; + type: "goal" | "yellow_card" | "red_card" | "substitution" | "own_goal"; + minute: number; + extra_time?: number; + player: { + id: string; + name: string; + }; + team: "home" | "away"; + assistedBy?: { + id: string; + name: string; + }; + replacedPlayer?: { + id: string; + name: string; + }; + reason?: string; // for cards: "violent conduct", "second yellow", etc. +} + +export interface Lineup { + team: "home" | "away"; + players: { + id: string; + name: string; + shirtNumber: number; + position: string; // GK, CB, LB, RB, CM, CM, LW, ST, etc. + isCaptain: boolean; + stats?: PlayerMatchStats; + }[]; + formation: string; // "4-3-3", "5-2-3", etc. + coach?: { + id: string; + name: string; + }; +} + +export interface PlayerMatchStats { + rating: number; // 0-10 + touches: number; + passes: number; + passAccuracy: number; // % + tackles: number; + interceptions: number; + dribbles: number; + dribbleSuccess: number; // % + shots: number; + shotsOnTarget: number; + keyPasses: number; + fouls: number; + offsides: number; +} + +export interface Match { + id: string; + homeTeam: Team; + awayTeam: Team; + homeScore: number; + awayScore: number; + status: MatchStatus; + kickoffTime: Date; + minute?: number; + competition: Competition; + round?: string; // "Round of 16", "Quarterfinals", etc. + venue?: { + name: string; + city: string; + country: string; + }; + events?: MatchEvent[]; + lineups?: { + home: Lineup; + away: Lineup; + }; +} + +// ============================================================================ +// PLAYER TYPES +// ============================================================================ + +export interface PlayerSeasonStats { + appearances: number; + goals: number; + assists: number; + yellowCards: number; + redCards: number; + minutesPlayed: number; + xG: number; // Expected Goals + xA: number; // Expected Assists + progressiveCarries: number; + progressivePasses: number; + pressures: number; + pressuresSuccessful: number; + dribbles: number; + dribblesSuccessful: number; + aerialDuels: number; + aerialDuelsWon: number; + tackles: number; + interceptions: number; + passCompletion: number; // % + shotsOnTarget: number; +} + +export interface PlayerFormMatch { + matchId: string; + opponent: string; + rating: number; // 0-10 + date: Date; + goals: number; + assists: number; + xG: number; + xA: number; +} + +export interface Player { + id: string; + name: string; + firstName?: string; + lastName?: string; + position: string; // "Goalkeeper", "Defender", "Midfielder", "Forward" + shirtNumber?: number; + dateOfBirth?: Date; + nationality?: string; + nationalityCode?: string; + height?: number; + weight?: number; + photo?: string; + club: { + id: string; + name: string; + logo?: string; + }; + contract?: { + start: Date; + end: Date; + }; + stats: PlayerSeasonStats; + formTrend?: PlayerFormMatch[]; // Last 10 matches + marketValue?: { + amount: number; + currency: string; + }; +} + +// ============================================================================ +// WORLD CUP TYPES +// ============================================================================ + +export interface WorldCupTeam extends Team { + group?: string; // A, B, C, etc. + groupPosition?: number; + gamesPlayed: number; + wins: number; + draws: number; + losses: number; + goalsFor: number; + goalsAgainst: number; + goalDifference: number; + points: number; + squad?: Player[]; +} + +export interface WorldCupGroup { + letter: string; // A, B, C, etc. + teams: WorldCupTeam[]; + matches?: Match[]; +} + +export interface KnockoutMatch extends Match { + round: "Round of 16" | "Quarterfinals" | "Semifinals" | "Final" | "Third Place"; + homeTeamPenalties?: number; + awayTeamPenalties?: number; + extraTime?: boolean; +} + +export interface WorldCupTournament { + year: number; + host: string; + groups: WorldCupGroup[]; + knockoutMatches?: KnockoutMatch[]; + winner?: Team; + runnerUp?: Team; + thirdPlace?: Team; + topScorer?: { + player: Player; + goals: number; + }; +} + +// ============================================================================ +// COMPETITION TYPES +// ============================================================================ + +export enum CompetitionCode { + PREMIER_LEAGUE = "PL", + LA_LIGA = "LA", + SERIE_A = "SA", + BUNDESLIGA = "BL", + LIGUE_1 = "L1", + WORLD_CUP = "WC", + CHAMPIONS_LEAGUE = "CL", + EUROPA_LEAGUE = "EL", +} + +export interface Competition { + code: CompetitionCode; + name: string; + country?: string; + type: "league" | "cup" | "international"; + season?: number; + logo?: string; + emblem?: string; + color?: string; // Accent color for the competition +} + +// ============================================================================ +// AI INSIGHT TYPES +// ============================================================================ + +export interface AIMatchPreviewRequest { + homeTeam: string; + awayTeam: string; + competition: string; + recentForm?: { + homeTeamForm: PlayerFormMatch[]; + awayTeamForm: PlayerFormMatch[]; + }; +} + +export interface AIPlayerSummaryRequest { + playerId: string; + playerName: string; + season?: number; +} + +export interface AIInsight { + id: string; + type: "match_preview" | "player_summary" | "trend_narrative"; + content: string; + relatedEntity: { + type: "match" | "player"; + id: string; + }; + generatedAt: Date; + tokens?: { + input: number; + output: number; + }; +} + +// ============================================================================ +// NEWS & ARTICLE TYPES +// ============================================================================ + +export interface NewsArticle { + id: string; + title: string; + summary: string; + content?: string; + image?: string; + source: string; + publishedAt: Date; + tags?: string[]; + relatedPlayers?: Player[]; + relatedTeams?: Team[]; + relatedMatches?: Match[]; +} + +// ============================================================================ +// API RESPONSE TYPES +// ============================================================================ + +export interface FixtureResponse { + fixtures: Match[]; + timestamp: Date; + total: number; + cached: boolean; + cacheHit?: boolean; +} + +export interface PlayerResponse { + player: Player; + timestamp: Date; + cached: boolean; +} + +export interface StatRadarData { + label: string; + playerValue: number; + leagueAverage: number; + maxValue?: number; +} + +// ============================================================================ +// UI STATE TYPES +// ============================================================================ + +export interface LoadingState { + isLoading: boolean; + error?: string; + message?: string; +} + +export interface PaginationState { + page: number; + pageSize: number; + total: number; + totalPages: number; +} + +// ============================================================================ +// FILTER & SEARCH TYPES +// ============================================================================ + +export interface MatchFilter { + competition?: CompetitionCode | CompetitionCode[]; + status?: MatchStatus | MatchStatus[]; + team?: string; + dateFrom?: Date; + dateTo?: Date; + limit?: number; + offset?: number; +} + +export interface PlayerFilter { + club?: string; + nationality?: string; + position?: string; + season?: number; + limit?: number; + offset?: number; + sort?: "rating" | "goals" | "assists" | "xG" | "marketValue"; + sortOrder?: "asc" | "desc"; +}