init
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# Скопируйте в .env — подойдут и имена из Next (NEXT_PUBLIC_*), и VITE_*.
|
||||
VITE_API_URL=https://example.com/api/
|
||||
NEXT_PUBLIC_API=https://example.com/api/
|
||||
|
||||
# База для полей image вида projects/uuid.jpg (со слэшем на конце).
|
||||
# Пример: https://storage.yandexcloud.net/dult-faib-knac-fint/
|
||||
VITE_S3_BUCKET=https://storage.yandexcloud.net/your-bucket/
|
||||
NEXT_PUBLIC_S3_BUCKET=https://storage.yandexcloud.net/your-bucket/
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
.env.local
|
||||
*.local
|
||||
@@ -0,0 +1,597 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "stream-demo-standalone",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
"framer-motion": "^11.17.0",
|
||||
"ky": "^1.4.0",
|
||||
"libphonenumber-js": "^1.11.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.53.0",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zustand": "^4.5.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^10.2.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"globals": "^17.4.0",
|
||||
"postcss": "^8.5.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.58.1",
|
||||
"vite": "^6.0.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
|
||||
|
||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.97.0", "", {}, "sha512-QdpLP5VzVMgo4VtaPppRA2W04UFjIqX+bxke/ZJhE5cfd5UPkRzqIAJQt9uXkQJjqE8LBOMbKv7f8HCsZltXlg=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.97.0", "", { "dependencies": { "@tanstack/query-core": "5.97.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-y4So4eGcQoK2WVMAcDNZE9ofB/p5v1OlKvtc1F3uqHwrtifobT7q+ZnXk2mRkc8E84HKYSlAE9z6HXl2V0+ySQ=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||
|
||||
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/type-utils": "8.58.1", "@typescript-eslint/utils": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.1", "@typescript-eslint/types": "^8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1" } }, "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.1", "@typescript-eslint/tsconfig-utils": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||
|
||||
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": "bin/autoprefixer" }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.17", "", { "bin": "dist/cli.cjs" }, "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": "cli.js" }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001787", "", {}, "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg=="],
|
||||
|
||||
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
|
||||
|
||||
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.334", "", {}, "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": "bin/esbuild" }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.4", "@eslint/config-helpers": "^0.5.4", "@eslint/core": "^1.2.0", "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA=="],
|
||||
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="],
|
||||
|
||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||
|
||||
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
||||
|
||||
"framer-motion": ["framer-motion@11.18.2", "", { "dependencies": { "motion-dom": "^11.18.1", "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jiti": ["jiti@1.21.7", "", { "bin": "bin/jiti.js" }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"ky": ["ky@1.14.3", "", {}, "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"libphonenumber-js": ["libphonenumber-js@1.12.41", "", {}, "sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA=="],
|
||||
|
||||
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"motion-dom": ["motion-dom@11.18.1", "", { "dependencies": { "motion-utils": "^11.18.1" } }, "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw=="],
|
||||
|
||||
"motion-utils": ["motion-utils@11.18.1", "", {}, "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
||||
|
||||
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="],
|
||||
|
||||
"postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
|
||||
|
||||
"postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
|
||||
|
||||
"postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
|
||||
|
||||
"postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
|
||||
|
||||
"postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
|
||||
|
||||
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="],
|
||||
|
||||
"react-hook-form": ["react-hook-form@7.72.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
|
||||
"react-swipeable": ["react-swipeable@7.0.2", "", { "peerDependencies": { "react": "^16.8.3 || ^17 || ^18 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-v1Qx1l+aC2fdxKa9aKJiaU/ZxmJ5o98RMoFwUqAAzVWUcxgfHFXDDruCKXhw6zIYXm6V64JiHgP9f6mlME5l8w=="],
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="],
|
||||
|
||||
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
|
||||
|
||||
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.58.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["immer"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import eslint from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import globals from "globals";
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser },
|
||||
},
|
||||
},
|
||||
reactHooks.configs.flat.recommended,
|
||||
{
|
||||
ignores: ["dist/**"],
|
||||
}
|
||||
);
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Stream Demo — GRAFF.estate</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2776
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "stream-demo-standalone",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
"framer-motion": "^11.17.0",
|
||||
"ky": "^1.4.0",
|
||||
"libphonenumber-js": "^1.11.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.53.0",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zustand": "^4.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^10.2.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"globals": "^17.4.0",
|
||||
"postcss": "^8.5.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.58.1",
|
||||
"vite": "^6.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 317 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,250 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, noarchive">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<title>Transfonter demo</title>
|
||||
<link href="stylesheet.css" rel="stylesheet">
|
||||
<style>
|
||||
/*
|
||||
http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
/* demo styles */
|
||||
body {
|
||||
background: #f0f0f0;
|
||||
color: #000;
|
||||
}
|
||||
.page {
|
||||
background: #fff;
|
||||
width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 20px 0 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.font-container {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
margin-bottom: 40px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
h1 {
|
||||
position: relative;
|
||||
background: #444;
|
||||
font-size: 32px;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
margin: 0 -20px 12px -20px;
|
||||
}
|
||||
.letters {
|
||||
font-size: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.s10:before {
|
||||
content: '10px';
|
||||
}
|
||||
.s11:before {
|
||||
content: '11px';
|
||||
}
|
||||
.s12:before {
|
||||
content: '12px';
|
||||
}
|
||||
.s14:before {
|
||||
content: '14px';
|
||||
}
|
||||
.s18:before {
|
||||
content: '18px';
|
||||
}
|
||||
.s24:before {
|
||||
content: '24px';
|
||||
}
|
||||
.s30:before {
|
||||
content: '30px';
|
||||
}
|
||||
.s36:before {
|
||||
content: '36px';
|
||||
}
|
||||
.s48:before {
|
||||
content: '48px';
|
||||
}
|
||||
.s60:before {
|
||||
content: '60px';
|
||||
}
|
||||
.s72:before {
|
||||
content: '72px';
|
||||
}
|
||||
.s10:before, .s11:before, .s12:before, .s14:before,
|
||||
.s18:before, .s24:before, .s30:before, .s36:before,
|
||||
.s48:before, .s60:before, .s72:before {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
color: #999;
|
||||
padding-right: 6px;
|
||||
}
|
||||
pre {
|
||||
display: block;
|
||||
padding: 9px;
|
||||
margin: 0 0 12px;
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.428571429;
|
||||
color: #333;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
overflow-x: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* responsive */
|
||||
@media (max-width: 959px) {
|
||||
.page {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="demo">
|
||||
<h1 style="font-family: 'TTHovesPro-DmBd'; font-weight: 600; font-style: normal;">☝︎TT Hoves Pro DemiBold</h1>
|
||||
<pre title="Usage">.your-style {
|
||||
font-family: 'TTHovesPro-DmBd';
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}</pre>
|
||||
<pre title="Preload (optional)">
|
||||
<link rel="preload" href="TTHovesPro-DmBd.woff2" as="font" type="font/woff2" crossorigin></pre>
|
||||
<div class="font-container" style="font-family: 'TTHovesPro-DmBd'; font-weight: 600; font-style: normal;">
|
||||
<p class="letters">
|
||||
abcdefghijklmnopqrstuvwxyz<br>
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
|
||||
0123456789.:,;()*!?'@#<>$%&^+-=~
|
||||
</p>
|
||||
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h1 style="font-family: 'TTHovesPro-Md'; font-weight: 500; font-style: normal;">☝︎TT Hoves Pro Medium</h1>
|
||||
<pre title="Usage">.your-style {
|
||||
font-family: 'TTHovesPro-Md';
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}</pre>
|
||||
<pre title="Preload (optional)">
|
||||
<link rel="preload" href="TTHovesPro-Md.woff2" as="font" type="font/woff2" crossorigin></pre>
|
||||
<div class="font-container" style="font-family: 'TTHovesPro-Md'; font-weight: 500; font-style: normal;">
|
||||
<p class="letters">
|
||||
abcdefghijklmnopqrstuvwxyz<br>
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
|
||||
0123456789.:,;()*!?'@#<>$%&^+-=~
|
||||
</p>
|
||||
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h1 style="font-family: 'TTHovesPro-Rg'; font-weight: normal; font-style: normal;">☝︎TT Hoves Pro Regular</h1>
|
||||
<pre title="Usage">.your-style {
|
||||
font-family: 'TTHovesPro-Rg';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}</pre>
|
||||
<pre title="Preload (optional)">
|
||||
<link rel="preload" href="TTHovesPro-Rg.woff2" as="font" type="font/woff2" crossorigin></pre>
|
||||
<div class="font-container" style="font-family: 'TTHovesPro-Rg'; font-weight: normal; font-style: normal;">
|
||||
<p class="letters">
|
||||
abcdefghijklmnopqrstuvwxyz<br>
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
|
||||
0123456789.:,;()*!?'@#<>$%&^+-=~
|
||||
</p>
|
||||
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,38 @@
|
||||
@font-face {
|
||||
font-family: 'TTHovesPro';
|
||||
src: url('TTHovesPro-DmBd.eot');
|
||||
src:
|
||||
url('TTHovesPro-DmBd.eot?#iefix') format('embedded-opentype'),
|
||||
url('TTHovesPro-DmBd.woff2') format('woff2'),
|
||||
url('TTHovesPro-DmBd.woff') format('woff'),
|
||||
url('TTHovesPro-DmBd.ttf') format('truetype');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'TTHovesPro';
|
||||
src: url('TTHovesPro-Md.eot');
|
||||
src:
|
||||
url('TTHovesPro-Md.eot?#iefix') format('embedded-opentype'),
|
||||
url('TTHovesPro-Md.woff2') format('woff2'),
|
||||
url('TTHovesPro-Md.woff') format('woff'),
|
||||
url('TTHovesPro-Md.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'TTHovesPro';
|
||||
src: url('TTHovesPro-Rg.eot');
|
||||
src:
|
||||
url('TTHovesPro-Rg.eot?#iefix') format('embedded-opentype'),
|
||||
url('TTHovesPro-Rg.woff2') format('woff2'),
|
||||
url('TTHovesPro-Rg.woff') format('woff'),
|
||||
url('TTHovesPro-Rg.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 12C0 5.37258 5.37258 0 12 0H48V36C48 42.6274 42.6274 48 36 48H12C5.37258 48 0 42.6274 0 36V12Z" fill="#B5F54E"/>
|
||||
<path d="M14.556 20.0507C14.2126 18.6521 15.7064 17.4123 17.6391 17.4123C19.3806 17.4123 21.1257 17.776 21.7876 20.044H24.0212V23.5056C21.239 21.3698 15.2073 22.7088 14.556 20.0473M37.7127 19.0885H32.6863L27.9042 23.9817V14.2086H24.0247V16.5791C23.8973 16.4271 23.7663 16.275 23.6141 16.1262C22.2017 14.731 20.17 14.0234 17.5718 14.0234C14.5666 14.0234 12.8569 15.2798 11.9508 16.3345C10.8287 17.6437 10.3261 19.4291 10.6729 20.8871C11.572 24.6562 15.3135 25.2579 18.0638 25.5919C20.2337 25.8564 22.3115 26.2233 22.2549 28.0318C22.1982 29.9065 19.9328 30.3693 18.4497 30.3693C14.5808 30.3693 14.4498 27.6979 14.4498 27.6979H10.2871C10.3473 28.6997 10.6729 30.3131 11.9932 31.6951C13.441 33.2094 15.6108 33.9764 18.4461 33.9764C20.6195 33.9764 22.6017 33.3152 24.0212 32.1381V33.7549H27.9007V29.1394L29.2741 27.7343L33.1925 33.7549H37.7091L32.0067 24.9372L37.7127 19.0952V19.0885Z" fill="#4C5658"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
Binary file not shown.
+14
@@ -0,0 +1,14 @@
|
||||
import { Footer } from "@/components/Layout/Footer";
|
||||
import StreamDemo from "@/features/stream-demo/StreamDemo";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="flex min-h-dvh flex-col">
|
||||
{/* Без overflow-clip: иначе flex-1 + clip часто даёт пустой/обрезанный экран */}
|
||||
<div className="min-h-0 flex-1 md:px-4 lg:px-[1.389vw] px-[10px] py-8 md:max-lg:pt-6 pt-4">
|
||||
<StreamDemo />
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { api } from "@/lib/api";
|
||||
import { projectsTags } from "@/consts/projectsTags";
|
||||
import type { Product } from "@/types/Product";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { CheckboxesGroup } from "@/ui/CheckboxesGroup";
|
||||
import { PhoneInputRu } from "@/ui/PhoneInputRu";
|
||||
import useAddReferer from "@/hooks/useAddReferer";
|
||||
import { useRefererStore } from "@/stores/useRefererStore";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useForm,
|
||||
} from "react-hook-form";
|
||||
import CheckIcon from "@/components/icons/CheckIcon";
|
||||
import FeedbackModal from "@/components/modals/FeedbackFormModal";
|
||||
|
||||
const DEFAULT_STREAM_DEMO_PRODUCTS = [
|
||||
"Удаленная демонстрация",
|
||||
] as Product[];
|
||||
|
||||
export function Feedback() {
|
||||
useAddReferer();
|
||||
|
||||
return (
|
||||
<div
|
||||
id="contacts"
|
||||
className="lg:mb-20 md:mb-12 lg:flex lg:gap-[0.833vw] max-lg:space-y-12 justify-between lg:mt-[14.07vh] mt-[100px] mb-10"
|
||||
>
|
||||
<h2 className="line2 font-medium max-lg:mb-6 lg:max-w-[45%]">
|
||||
<span className="text-[#7A7A7A]">Хотите увеличить конверсию?</span>
|
||||
<br />
|
||||
Давайте обсудим детали.
|
||||
</h2>
|
||||
<FeedbackForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface IInput {
|
||||
fullname: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
products: Product[];
|
||||
referer?: string | null;
|
||||
}
|
||||
|
||||
export function FeedbackForm() {
|
||||
const { referer } = useRefererStore();
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const form = useForm<IInput>({
|
||||
defaultValues: {
|
||||
fullname: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
products: DEFAULT_STREAM_DEMO_PRODUCTS,
|
||||
},
|
||||
});
|
||||
|
||||
const { register, handleSubmit, formState, control } = form;
|
||||
|
||||
async function onSubmit(data: IInput) {
|
||||
const { id } = await api
|
||||
.post("mail", { json: { ...data, referer } })
|
||||
.json<{ id: string }>();
|
||||
setModal(<FeedbackModal id={id} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 lg:max-w-[47.431vw]">
|
||||
<div className="space-y-10">
|
||||
{!formState.isSubmitted ? (
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
className="lg:space-y-[1.944vw] md:max-lg:space-y-7 space-y-3"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="lg:space-y-[1.111vw] space-y-4">
|
||||
<p className="heading2 font-medium">Нам нужно</p>
|
||||
<CheckboxesGroup name="products" options={projectsTags} />
|
||||
</div>
|
||||
<input
|
||||
id="name"
|
||||
autoComplete="none"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Имя*"
|
||||
{...register("fullname")}
|
||||
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<input
|
||||
autoComplete="none"
|
||||
required
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Email*"
|
||||
{...register("email")}
|
||||
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none btnl outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<div className="flex gap-x-3 py-2 border-[#3D425C] relative">
|
||||
<Controller
|
||||
name="phone"
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<PhoneInputRu
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
inputRef={field.ref}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="bottom-0 absolute w-full border-b border-[#37393B] peer-focus:border-white -mb-2" />
|
||||
</div>
|
||||
<div className="md:flex items-stretch lg:gap-[0.833vw] gap-3">
|
||||
<Button
|
||||
type="submit"
|
||||
className="btnl max-md:mb-3 max-md:w-full lg:px-[2.222vw] lg:py-[1.389vw] px-8 py-5 cursor-pointer lg:rounded-[1.111vw] rounded-2xl"
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
<div className="text2 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1">
|
||||
<span className="text-[#7A7A7A]">
|
||||
*Нажимая кнопку отправить, вы даете
|
||||
</span>{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={"/privacy-policy"}
|
||||
className="underline"
|
||||
>
|
||||
согласие на обработку персональных данных
|
||||
</a>{" "}
|
||||
<span className="text-[#7A7A7A]">и принимаете </span>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={"/policy"}
|
||||
className="underline"
|
||||
>
|
||||
условия политики
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
) : (
|
||||
<div className="bg-[#37393B99] aspect-[643/480] w-full rounded-2xl flex justify-center items-center">
|
||||
<div className="flex gap-3 justify-center items-center">
|
||||
<div className="bg-gradient p-3 rounded-full">
|
||||
<div className="text-white lg:size-[1.667vw] size-6">
|
||||
<CheckIcon />
|
||||
</div>
|
||||
</div>
|
||||
<p className="heading2 font-medium">
|
||||
Мы получили заявку
|
||||
<br />и скоро свяжемся с вами!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import ArrowMoreIcon from "@/components/icons/ArrowMoreIcon";
|
||||
import RutubeIcon from "@/components/icons/RutubeIcon";
|
||||
import TelegramIcon from "@/components/icons/TgIcon";
|
||||
import VkIcon from "@/components/icons/VKIcon";
|
||||
import YoutubeIcon from "@/components/icons/YoutubeIcon";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="lg:px-5 lg:pb-5 md:max-lg:px-4 md:max-lg:pb-4 px-[10px] pb-[10px] space-y-6 mb-0">
|
||||
<div className="max-md:flex-col lg:gap-[0.833vw] md:max-lg:gap-[1.042vw] flex gap-[1.111vw]">
|
||||
<a
|
||||
href={"tel:" + "8 800 770 00 67".replaceAll(" ", "")}
|
||||
className="lg:p-[1.667vw] p-6 flex flex-col justify-between max-md:gap-y-10 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] lg:rounded-[1.111vw] rounded-2xl lg:aspect-[696/248] lg:w-[48.333vw] md:max-lg:w-[47.656vw] hover:bg-[#37393B99] bg-transparent transition-colors"
|
||||
>
|
||||
<div className="text-[#7A7A7A] text1 font-medium">Позвонить</div>
|
||||
<div className="lg:line2 md:max-lg:heading1 line2 flex items-center font-medium">
|
||||
8 800 770 00 67
|
||||
<div className="text-white lg:size-[5.556vw] size-[10vw]">
|
||||
<ArrowMoreIcon />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href={"mailto:" + "info@graff.tech"}
|
||||
className="lg:p-[1.667vw] p-6 flex flex-col justify-between max-md:gap-y-10 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] lg:rounded-[1.111vw] rounded-2xl lg:aspect-[624/248] lg:w-[43.333vw] md:max-lg:w-[47.656vw] hover:bg-[#37393B99] bg-transparent transition-colors"
|
||||
>
|
||||
<div className="text-[#7A7A7A] text1 font-medium">Написать</div>
|
||||
<div className="lg:line2 md:max-lg:heading1 line2 flex items-center font-medium">
|
||||
info@graff.tech
|
||||
<div className="text-white lg:size-[5.556vw] size-[10vw]">
|
||||
<ArrowMoreIcon />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div className="gap-y-2 justify-between md:gap-x-[1.042vw] gap-x-[1.111vw] md:flex-col flex">
|
||||
<ContactLink href="https://t.me/graffestate">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<TelegramIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
<ContactLink href="https://rutube.ru/channel/25505040">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<RutubeIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
<ContactLink href="https://vk.com/graff.estate">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<VkIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
<ContactLink href="https://www.youtube.com/@GRAFFtech">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<YoutubeIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:w-full flex flex-col md:flex-row md:max-lg:gap-[1.042vw] lg:gap-x-[0.833vw] gap-6 lg:pb-6 max-lg:py-6 pb-10 md:max-lg:pt-4 !max-md:mt-[11.111vw] border-b border-[#232425] relative">
|
||||
<div className="flex flex-col gap-y-[1.111vw] lg:min-w-[48.193vw] flex-1">
|
||||
<span className=" text1 text-[#7A7A7A]">Юридический адрес:</span>
|
||||
<span className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]">
|
||||
620063, г. Екатеринбург, <br /> ул. Большакова, д. 66, кв. 6
|
||||
</span>
|
||||
|
||||
<div className="flex flex-col gap-y-[1.111vw] lg:mt-[0px] md:mt-[2.083vw] mt-6">
|
||||
<span className=" text1 text-[#7A7A7A]">Наш основной стек:</span>
|
||||
<div className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]">
|
||||
<p>Unreal Engine 5, C++</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-[1.111vw] flex-1">
|
||||
<span className="text1 text-[#7A7A7A]">Реквизиты:</span>
|
||||
<div className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]">
|
||||
<p>ИНН: 6679174128</p>
|
||||
<p>КПП: 667101001</p>
|
||||
<p>ООО "ГРАФФ.ЭСТЕЙТ"</p>
|
||||
<p>ОГРН 1246600010140</p>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="/img/components/header/Sk.svg"
|
||||
alt="Сколково"
|
||||
className=" lg:hidden md:size-[6.25vw] size-[13.333vw] max-md:absolute max-md:right-0 max-md:bottom-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="lg:gap-x-[0.833vw] gap-y-2 flex max-lg:flex-col">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={"/policy"}
|
||||
className="text-[#37393B] text1 font-medium leading-[18.9px] lg:w-[48.193vw] w-fit"
|
||||
>
|
||||
Политика конфиденциальности и обработки персональных данных
|
||||
</a>
|
||||
<p className="text-[#37393B] text1 font-medium leading-[18.9px] col-start-1">
|
||||
© 2026 GRAFF interactive. Все права защищены
|
||||
</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={"https://graff.tech"}
|
||||
className="text-[#37393B] text1 font-medium leading-[18.9px] lg:ml-auto w-fit md:col-start-2 md:text-right"
|
||||
>
|
||||
graff.tech
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContactLink({
|
||||
children,
|
||||
href,
|
||||
className = "",
|
||||
}: PropsWithChildren<{ href: string; className?: string }>) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`lg:rounded-[1.111vw] rounded-2xl bg-[#37393B99] lg:p-[1.25vw] p-[18px] hover:bg-white transition-all hover:text-black flex justify-center w-full group ${className}`}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
interface BRProps {
|
||||
lg?: boolean;
|
||||
md?: boolean;
|
||||
sm?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function BR({ lg, md, sm, className }: BRProps) {
|
||||
const modifier =
|
||||
!lg && !md && !sm
|
||||
? ""
|
||||
: `lg:${lg ? `block` : "hidden"} md:${md ? "block" : "hidden"} ${
|
||||
sm ? "block" : "hidden"
|
||||
} `;
|
||||
const combinedClassName = `${modifier} ${className ?? ""}`;
|
||||
|
||||
return <br className={combinedClassName} />;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
export function ModalContainer() {
|
||||
const { modal, setModal } = useModalStore();
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") setModal(null);
|
||||
};
|
||||
document.addEventListener("keydown", listener);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", listener);
|
||||
};
|
||||
}, [setModal]);
|
||||
|
||||
const jsx = modal ? (
|
||||
<div className="fixed left-0 z-[20] w-full h-full flex justify-center items-start transition-opacity">
|
||||
<div className="absolute [backdrop-filter:blur(16px)] bg-[#0F101199] w-full h-full z-[1]" />
|
||||
{modal}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return createPortal(jsx, document.body);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
function ArrowMoreIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="m15.588 9.59-8.52 8.52v-2.94l7.049-7.05H6.332V6.04h11.336v11.336h-2.08z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArrowMoreIcon;
|
||||
@@ -0,0 +1,14 @@
|
||||
function CheckIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="m9.707 18 9.707-9.707L18 6.879l-8.293 8.293-4.293-4.293L4 12.293z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default CheckIcon;
|
||||
@@ -0,0 +1,14 @@
|
||||
function CloseIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.343 4.929 12 10.586l5.657-5.657 1.414 1.414L13.414 12l5.657 5.657-1.414 1.414L12 13.414l-5.657 5.657-1.414-1.414L10.586 12 4.929 6.343z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default CloseIcon;
|
||||
@@ -0,0 +1,12 @@
|
||||
function MuteIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.74 3.993a1 1 0 1 0-1.48 1.345l2.116 2.328h-2.71A1.667 1.667 0 0 0 2 9.333v5.333a1.667 1.667 0 0 0 1.667 1.667h3.656l5.73 4.456A1 1 0 0 0 14.667 20v-3.214l2.926 3.22a1 1 0 0 0 1.48-1.345zM4 9.666h2.667v4.667H4zm8.667 8.289-4-3.111v-4.658l4 4.397zM10.083 6.788a1 1 0 0 1 .176-1.404l2.793-2.173A1 1 0 0 1 14.667 4v5.245a1 1 0 0 1-2 0v-3.2l-1.18.917a1 1 0 0 1-1.404-.175zM16.25 10.9a1 1 0 0 1 1.5-1.32 3.67 3.67 0 0 1 .462 4.184 1 1 0 0 1-1.75-.963 1.67 1.67 0 0 0-.212-1.903zM22 12a7 7 0 0 1-1.593 4.446 1.002 1.002 0 0 1-1.663-.16 1 1 0 0 1 .12-1.111 5 5 0 0 0-.137-6.509 1.001 1.001 0 1 1 1.49-1.333A7 7 0 0 1 22 11.999"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuteIcon;
|
||||
@@ -0,0 +1,12 @@
|
||||
function PauseIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10 3H7L5 5v16h3l2-2zm9 0h-3l-2 2v16h3l2-2z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default PauseIcon;
|
||||
@@ -0,0 +1,9 @@
|
||||
function PlayIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 12 6 21V3z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlayIcon;
|
||||
@@ -0,0 +1,12 @@
|
||||
function RutubeIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M16.19 10.815H6.928V7.308h9.262c.54 0 .917.09 1.106.248.189.157.306.45.306.877v1.259c0 .45-.117.742-.306.9-.189.157-.565.224-1.106.224m.635-6.814H3V19h3.928v-4.88h7.239L17.602 19H22l-3.787-4.903c1.396-.198 2.023-.607 2.54-1.282s.776-1.753.776-3.193V8.497c0-.854-.094-1.529-.259-2.046a3.4 3.4 0 0 0-.846-1.37 3.9 3.9 0 0 0-1.459-.833C18.4 4.09 17.695 4 16.825 4z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default RutubeIcon;
|
||||
@@ -0,0 +1,14 @@
|
||||
function TgIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.27 11.886a640 640 0 0 1 9.65-4.47C17.511 5.36 18.467 5 19.086 5c.13 0 .439.028.645.194.155.14.207.334.232.472.026.14.052.445.026.695-.258 2.804-1.316 9.662-1.883 12.8-.233 1.333-.697 1.777-1.136 1.833-.954.083-1.702-.694-2.631-1.333-1.445-1.027-2.271-1.666-3.69-2.666-1.626-1.166-.568-1.805.361-2.832.232-.277 4.49-4.415 4.567-4.804 0-.055.026-.222-.077-.305-.104-.083-.232-.056-.336-.028-.155.028-2.477 1.694-6.992 4.97-.671.5-1.265.723-1.806.723-.594 0-1.73-.361-2.58-.667-1.033-.36-1.858-.555-1.781-1.166.077-.333.49-.667 1.264-1"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default TgIcon;
|
||||
@@ -0,0 +1,12 @@
|
||||
function UnmutedIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14.106 3.102a1 1 0 0 0-1.053.109l-5.73 4.456H3.667A1.667 1.667 0 0 0 2 9.333v5.334a1.667 1.667 0 0 0 1.667 1.666h3.656l5.73 4.456A1 1 0 0 0 14.667 20V4a1 1 0 0 0-.561-.898M4 9.667h2.667v4.666H4zm8.667 8.288-4-3.11v-5.69l4-3.11zm6-5.955c0 .893-.326 1.756-.917 2.426a1 1 0 0 1-1.5-1.324 1.67 1.67 0 0 0 0-2.202 1 1 0 0 1 1.5-1.322c.59.669.916 1.53.917 2.422M22 12a7 7 0 0 1-1.782 4.667 1 1 0 0 1-1.491-1.334 5 5 0 0 0 0-6.666 1 1 0 1 1 1.49-1.334A7 7 0 0 1 22 12"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnmutedIcon;
|
||||
@@ -0,0 +1,14 @@
|
||||
function VKIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.085 6H1.788C1.13 6 1 6.297 1 6.625c0 .586.779 3.49 3.627 7.33C6.525 16.578 9.2 18 11.634 18c1.46 0 1.64-.316 1.64-.86v-1.982c0-.632.139-.758.602-.758.34 0 .925.164 2.288 1.429C17.72 17.327 17.978 18 18.854 18h2.298c.656 0 .984-.316.795-.94-.207-.62-.95-1.521-1.938-2.59-.536-.608-1.34-1.264-1.582-1.592-.341-.421-.243-.609 0-.983 0 0 2.799-3.794 3.092-5.082.145-.469 0-.813-.696-.813h-2.297c-.584 0-.853.297-.999.625 0 0-1.168 2.74-2.823 4.52-.536.515-.78.68-1.071.68-.146 0-.357-.165-.357-.633v-4.38c0-.561-.17-.812-.657-.812H9.01c-.365 0-.584.26-.584.508 0 .533.827.656.912 2.154v3.256c0 .714-.134.843-.427.843-.778 0-2.673-2.752-3.796-5.901-.22-.614-.442-.86-1.029-.86"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default VKIcon;
|
||||
@@ -0,0 +1,14 @@
|
||||
function YoutubeIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M19.814 5.423a2.52 2.52 0 0 1 1.768 1.79C22 8.79 22 12.082 22 12.082s0 3.293-.418 4.871a2.52 2.52 0 0 1-1.768 1.79c-1.56.423-7.814.423-7.814.423s-6.254 0-7.814-.423a2.52 2.52 0 0 1-1.768-1.79C2 15.377 2 12.084 2 12.084s0-3.294.418-4.872a2.52 2.52 0 0 1 1.768-1.79C5.746 5 12 5 12 5s6.254 0 7.814.423m-9.48 3.744V15l5-2.917z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default YoutubeIcon;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { api } from "@/lib/api";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { modalOptions } from "@/consts/feedback";
|
||||
import CustomCheckbox from "@/ui/CustomCheckbox";
|
||||
import CheckIcon from "@/components/icons/CheckIcon";
|
||||
|
||||
function FeedbackModal({ id }: { id: string }) {
|
||||
const { setModal } = useModalStore();
|
||||
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
|
||||
|
||||
async function sendSources() {
|
||||
await api.put(`mail/${id}`, { json: { source: selectedOptions } });
|
||||
setModal(null);
|
||||
}
|
||||
|
||||
function onCheckboxChange(value: string, checked: boolean) {
|
||||
if (checked) setSelectedOptions([...selectedOptions, value]);
|
||||
else {
|
||||
const updated = [...selectedOptions].filter((item) => item !== value);
|
||||
setSelectedOptions(updated);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed top-[50%] translate-y-[-50%] flex md:flex-row flex-col md:max-w-[695px] max-w-[422px] max-[360px]:max-w-[340px] z-[15] md:p-[48px] p-[32px] max-[360px]:p-[24px] bg-[#37393B99] backdrop-blur-xl backdrop-opacity-60 rounded-2xl">
|
||||
<div className="flex md:flex-col flex-row md:justify-center items-center md:max-w-[200px] md:gap-y-[16px] gap-x-[24px]">
|
||||
<div className="p-3 rounded-full bg-gradient translate-x-[4px]">
|
||||
<div className="z-10 absolute top-[-4px] left-[-4.3px] w-[56px] h-[56px] rounded-full bg-gradient-to-r from-[#6078F299] to-[#C868F599]" />
|
||||
<div className="text-white lg:size-[1.389vw] size-4 relative z-20">
|
||||
<CheckIcon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-xl leading-6 md:text-center text-start max-[360px]:text-base">
|
||||
Мы получили заявку <br className="md:hidden block" /> и скоро свяжемся{" "}
|
||||
<br className="md:block hidden" /> с вами!
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="md:w-[5px] md:mx-[48px] md:my-[0px] md:h-auto h-[2px] my-[24px] bg-[#37393B] rounded-sm "></div>
|
||||
|
||||
<div>
|
||||
<div className="text-xl leading-6 mb-[20px] max-w-[250px] max-[360px]:text-base">
|
||||
Расскажите, пожалуйста, <br /> откуда вы узнали о нас?
|
||||
</div>
|
||||
|
||||
<ul className="md:mb-[49px] mb-[58px] flex flex-col gap-y-[12px]">
|
||||
{modalOptions.map((item, index) => (
|
||||
<li key={index} className="flexfont-normal">
|
||||
<CustomCheckbox value={item} onChange={onCheckboxChange} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="flex flex-row gap-x-[12px] ">
|
||||
<Button
|
||||
onClick={sendSources}
|
||||
className="md:px-[31px] max-[360px]:px-[32px] px-[43px] py-[15px] rounded-2xl max-[360px]:text-sm"
|
||||
>
|
||||
Отправить
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setModal(null)}
|
||||
className="md:px-[31px] px-[43px] max-[360px]:px-[32px] py-[15px] rounded-2xl bg-[#37393B] max-[360px]:text-sm"
|
||||
color="secondary"
|
||||
>
|
||||
Пропустить
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FeedbackModal;
|
||||
@@ -0,0 +1,165 @@
|
||||
import React, { useRef } from "react";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { PhoneInputRu } from "@/ui/PhoneInputRu";
|
||||
import { useOnClickOutside } from "usehooks-ts";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import CheckIcon from "@/components/icons/CheckIcon";
|
||||
import { projectsTags } from "@/consts/projectsTags";
|
||||
import { CheckboxesGroup } from "@/ui/CheckboxesGroup";
|
||||
import { Product } from "@/types/Product";
|
||||
import FeedbackModal from "./FeedbackFormModal";
|
||||
import { api } from "@/lib/api";
|
||||
import { useRefererStore } from "@/stores/useRefererStore";
|
||||
import CloseIcon from "@/components/icons/CloseIcon";
|
||||
|
||||
interface IInput {
|
||||
fullname: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
products1: Product[];
|
||||
referer?: string | null;
|
||||
}
|
||||
|
||||
interface IQuestionFormModalProps {
|
||||
products?: Product[];
|
||||
}
|
||||
|
||||
export default function QuestionFormModal({
|
||||
products,
|
||||
}: IQuestionFormModalProps) {
|
||||
const { referer } = useRefererStore();
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const formRef = useRef(null);
|
||||
|
||||
useOnClickOutside(formRef, () => {
|
||||
setModal(null);
|
||||
});
|
||||
|
||||
const form = useForm<IInput>({
|
||||
defaultValues: {
|
||||
phone: "",
|
||||
products1:
|
||||
products || (["Создание сайтов", "Веб-тур по 360 сферам"] as Product[]),
|
||||
},
|
||||
});
|
||||
|
||||
const { register, handleSubmit, formState, control } = form;
|
||||
|
||||
async function onSubmit(data: IInput) {
|
||||
const { id } = await api
|
||||
.post("mail", { json: { ...data, products: data.products1, referer } })
|
||||
.json<{ id: string }>();
|
||||
setModal(<FeedbackModal id={id} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={formRef}
|
||||
className="p-[3.333vw] w-[64.514vw] backdrop-blur-[20px] rounded-[1.111vw] z-10 bg-[radial-gradient(circle_at_bottom_right,rgba(24,25,26,0.84)_0%,rgba(45,46,47,0.86)_100%)] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-md:w-[94.444vw] max-md:px-[6.667vw] max-md:py-[5.556vw] max-md:rounded-[4.444vw]"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setModal(null)}
|
||||
className="absolute lg:p-4 p-0 lg:top-[1.667vw] md:top-[3.125vw] top-[3.333vw] right-[3.333vw] md:right-[3.125vw] cursor-pointer"
|
||||
>
|
||||
<div className="text-white lg:size-[1.667vw] md:size-[3.125vw] size-[6.667vw]">
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</button>
|
||||
{!formState.isSubmitted ? (
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
className="lg:space-y-[1.944vw] md:max-lg:space-y-7 space-y-3"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="lg:space-y-[1.111vw] space-y-4">
|
||||
<p className="heading2 font-medium">Нам нужно</p>
|
||||
<CheckboxesGroup name="products1" options={projectsTags} />
|
||||
</div>
|
||||
<input
|
||||
id="name"
|
||||
autoComplete="none"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Имя*"
|
||||
{...register("fullname")}
|
||||
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<input
|
||||
autoComplete="none"
|
||||
required
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Email*"
|
||||
{...register("email")}
|
||||
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none btnl outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<div className="flex gap-x-3 py-2 border-[#3D425C] relative">
|
||||
<Controller
|
||||
name="phone"
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<PhoneInputRu
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
inputRef={field.ref}
|
||||
placeholder="+X (XXX) XXX - XX - XX"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="bottom-0 absolute w-full border-b border-[#37393B] peer-focus:border-white -mb-2" />
|
||||
</div>
|
||||
<div className="md:flex items-stretch lg:gap-[0.833vw] gap-3">
|
||||
<Button
|
||||
type="submit"
|
||||
className="btnl max-md:mb-3 max-md:w-full lg:px-[2.222vw] lg:py-[1.389vw] px-8 py-5 cursor-pointer lg:rounded-[1.111vw] rounded-2xl"
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
<div className="text2 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1">
|
||||
<span className="text-[#7A7A7A]">
|
||||
*Нажимая кнопку отправить, вы даете
|
||||
</span>{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={"/privacy-policy"}
|
||||
className="underline"
|
||||
>
|
||||
согласие на обработку персональных данных
|
||||
</a>{" "}
|
||||
<span className="text-[#7A7A7A]">и принимаете </span>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={"/policy"}
|
||||
className="underline"
|
||||
>
|
||||
условия политики
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
) : (
|
||||
<div className="bg-[#37393B99] aspect-[643/480] w-full rounded-2xl flex justify-center items-center">
|
||||
<div className="flex gap-3 justify-center items-center">
|
||||
<div className="bg-gradient p-3 rounded-full">
|
||||
<div className="text-white lg:size-[1.667vw] size-6">
|
||||
<CheckIcon />
|
||||
</div>
|
||||
</div>
|
||||
<p className="heading2 font-medium">
|
||||
Мы получили заявку
|
||||
<br />и скоро свяжемся с вами!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const modalOptions: string[] = [
|
||||
"Увидели на выставке или форуме",
|
||||
"Видели у других застройщиков",
|
||||
"Из рейтингов и статей",
|
||||
"Нашли в интернете",
|
||||
"Перешли по рекламе",
|
||||
"Из рассылки",
|
||||
"Другое",
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Product } from "@/types/Product";
|
||||
|
||||
export const projectsTags: Product[] = [
|
||||
"Интерактивная презентация",
|
||||
"Удаленная демонстрация",
|
||||
"Архитектурная визуализация",
|
||||
"Создание сайтов",
|
||||
"Веб-тур по 360 сферам",
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
export const streaming = [
|
||||
{
|
||||
title: "ЖК «Риваят»",
|
||||
url: "https://stream.graff.tech/?build=PortovayaDev&location=a1",
|
||||
},
|
||||
{
|
||||
title: "БК «Прокшино»",
|
||||
url: "https://stream.graff.tech/?build=Prokshino&location=a1",
|
||||
},
|
||||
{
|
||||
title: "ЖК «DNS Сити»",
|
||||
url: "https://stream.graff.tech/?build=DNScity&location=a1",
|
||||
},
|
||||
{
|
||||
title: "ЖК «Upside Towers»",
|
||||
url: "https://stream.graff.tech/?build=upsideTowersDev&location=a1",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,114 @@
|
||||
import React, { useState } from "react";
|
||||
import BR from "@/components/Layout/LineBreak";
|
||||
import { useGetProjectsQuery } from "@/queries/getProjects";
|
||||
import { StreamingProject } from "./StreamingProject";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
|
||||
export default function AvailableDemos() {
|
||||
const { isMd, isLg } = useMediaQueries();
|
||||
const { data: streamingProjects } = useGetProjectsQuery(
|
||||
"Удаленная демонстрация"
|
||||
);
|
||||
const [current, setCurrent] = useState(0);
|
||||
|
||||
const projects = streamingProjects ?? [];
|
||||
|
||||
const slideCount = Math.min(projects.length + 1, 4);
|
||||
|
||||
const handlers = useSwipeable({
|
||||
onSwipedLeft: () =>
|
||||
setCurrent((prev) => (prev + 1) % Math.max(slideCount, 1)),
|
||||
onSwipedRight: () =>
|
||||
setCurrent(
|
||||
(prev) =>
|
||||
(prev + Math.min(projects.length, 4)) % Math.max(slideCount, 1)
|
||||
),
|
||||
trackMouse: true,
|
||||
preventScrollOnSwipe: true,
|
||||
touchEventOptions: { passive: false },
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex lg:flex-row flex-col lg:mb-[4.444vw] md:mb-[8.333vw] md:gap-[3.125vw]">
|
||||
<h2 className="line2 max-md:line1 w-full max-md:mb-[5.556vw]">
|
||||
Доступные <BR lg sm /> демонстрации
|
||||
</h2>
|
||||
|
||||
{!isLg && !isMd && (
|
||||
<div
|
||||
className="lg:grid md:flex grid-cols-4 gap-3 px-5 md:-mx-5 md:overflow-auto [scrollbar-width:none] relative max-md:aspect-[340/344] [transform-style:preserve-3d] items-stretch mb-[5.556vw]"
|
||||
{...handlers}
|
||||
>
|
||||
{projects.slice(0, 3).map((project, index, { length }) => (
|
||||
<StreamingProject
|
||||
key={project.id}
|
||||
{...project}
|
||||
index={index}
|
||||
current={current}
|
||||
count={length + 1}
|
||||
href="/"
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className={`bg-gradient-to-r from-[#FFFFFF14] to-[#FFFFFF00] [background:linear-gradient(to_right,#FFFFFF14,#FFFFFF00)] p-0.5 lg:rounded-[1.111vw] rounded-2xl flex flex-1 justify-center !duration-500 items-center md:min-w-[300px] group max-md:absolute self-stretch max-md:h-full transition-[scale,transform] will-change-[transform,scale] select-none max-md:w-[calc(100%-40px)] max-md:bg-[#0F101199] max-md:[backdrop-filter:blur(40px)] ${
|
||||
slideCount - 1 === current
|
||||
? "max-md:[transform:translateZ(40px)]"
|
||||
: "max-md:[scale:85%]"
|
||||
} ${
|
||||
slideCount - 1 === (current + 1) % slideCount
|
||||
? "max-md:translate-x-[calc(7.5%+20px)]"
|
||||
: slideCount - 1 ===
|
||||
(current - 1 + slideCount) % slideCount
|
||||
? "max-md:translate-x-[calc(-7.5%-20px)]"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="md:bg-[#0F1011] h-full w-full lg:rounded-[1.111vw] rounded-2xl flex items-center p-6">
|
||||
<div className="flex flex-col items-center space-y-6">
|
||||
<p className="heading2 font-medium text-center">
|
||||
Расскажем и покажем как это работает на созвоне
|
||||
</p>
|
||||
<a
|
||||
href="/form"
|
||||
className="btnm font-medium group-hover:scale-105 duration-500 lg:px-[1.667vw] lg:py-[1.181vw] px-6 py-[17px] transition-transform lg:rounded-[0.833vw] rounded-xl bg-gradient"
|
||||
>
|
||||
Оставить заявку
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="lg:headline1 headline2 text-[#7A7A7A] w-full pr-[8.333vw] md:max-w-[75vw]">
|
||||
Клиент из любой точки мира может посмотреть жилой комплекс, даже на
|
||||
нулевом этапе строительства. Он выберет лучшую планировку и оценит вид
|
||||
из окон своей будущей квартиры.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{(isLg || isMd) && (
|
||||
<div className="flex md:-mx-[2.604vw] md:w-[calc(100%+5.208vw)] md:px-[2.604vw] lg:gap-[0.833vw] md:gap-[1.563vw] lg:h-[27.5vw] md:h-[51.563vw] md:overflow-x-scroll hide-scrollbars">
|
||||
{projects.slice(0, 3).map((project, index, { length }) => (
|
||||
<div
|
||||
key={project.id}
|
||||
className={`w-full ${index === 0 ? "flex-auto" : "flex-1"}`}
|
||||
>
|
||||
<StreamingProject
|
||||
key={project.id}
|
||||
{...project}
|
||||
index={index}
|
||||
current={current}
|
||||
count={length + 1}
|
||||
href="/"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import BR from "@/components/Layout/LineBreak";
|
||||
import { Button } from "@/ui/Button";
|
||||
import QuestionFormModal from "@/components/modals/QuestionFormModal";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
|
||||
export default function RequestForDemo() {
|
||||
const { setModal } = useModalStore();
|
||||
return (
|
||||
<div
|
||||
className="
|
||||
flex max-md:flex-col flex-row lg:gap-[0.833vw] md:gap-[2.865vw] gap-[11.111vw]
|
||||
lg:-mx-[1.389vw] lg:w-[calc(100%+2.778vw)] lg:pl-[1.389vw]
|
||||
"
|
||||
>
|
||||
<div className="flex flex-col lg:max-w-[31.944vw] min-h-full">
|
||||
<h2 className="line2 max-md:mb-[6.667vw]">
|
||||
Запись <BR lg md /> на удаленную
|
||||
<BR /> демонстрацию
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col lg:gap-[2.222vw] md:gap-[4.167vw] mt-auto">
|
||||
<p className="lg:headline1 headline2 text-[#7A7A7A]">
|
||||
Запись на демонстрацию может быть оформлена в виде блока на сайте
|
||||
застройщика или жилого комплекса.
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setModal(
|
||||
<QuestionFormModal products={["Удаленная демонстрация"]} />
|
||||
);
|
||||
}}
|
||||
className="max-md:hidden btnl bg-gradient-saturated lg:py-[1.389vw] lg:px-[2.222vw] md:py-[2.604vw] md:px-[4.167vw] md:rounded-[2.083vw] lg:rounded-[1.111vw]"
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src="/img/pages/stream-demo/showreel.png"
|
||||
alt=""
|
||||
className="lg:h-[44.444vw] md:h-[57.292vw] h-[122.222vw] max-md:rounded-[4.444vw] object-cover"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import { Feedback } from "@/components/Layout/Feedback";
|
||||
import AvailableDemos from "./AvailableDemos";
|
||||
import RequestForDemo from "./RequestForDemo";
|
||||
import StreamPlayer from "./StreamPlayer";
|
||||
|
||||
export default function StreamDemo() {
|
||||
return (
|
||||
<div className="lg:space-y-[9.722vw] md:space-y-[13.021vw] space-y-[27.778vw]">
|
||||
<AvailableDemos />
|
||||
<RequestForDemo />
|
||||
<div className="w-full shrink-0 aspect-[340/600] max-h-[85dvh] md:max-lg:aspect-[736/480] md:max-lg:max-h-[80dvh] lg:aspect-auto lg:h-[44.444vw] lg:max-h-none">
|
||||
<StreamPlayer className="h-full min-h-[12rem]" />
|
||||
</div>
|
||||
<Feedback />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import { resolvePublicPath } from "@/lib/env";
|
||||
import { VideoPlayer } from "@/ui/VideoPlayer";
|
||||
|
||||
const STREAMING_VIDEO_SRC = resolvePublicPath(
|
||||
"/videos/pages/home/streaming.mp4"
|
||||
);
|
||||
|
||||
export default function StreamPlayer({ className }: { className?: string }) {
|
||||
return (
|
||||
<div className={`w-full ${className ?? ""}`}>
|
||||
<VideoPlayer
|
||||
src={STREAMING_VIDEO_SRC}
|
||||
showMutingBtn
|
||||
loop
|
||||
autoPlay
|
||||
muted
|
||||
className="lg:aspect-[1400/640] max-h-dvh md:max-lg:aspect-[736/480] aspect-[340/600]"
|
||||
>
|
||||
<p className="absolute font-medium md:bottom-6 md:left-6 bottom-4 left-4 lg:max-w-[40%] md:max-lg:max-w-[80%] accent">
|
||||
GRAFF.estate — модуль удаленной демонстрации — доступен на любых
|
||||
устройствах, для демонстрации нужен только интернет
|
||||
</p>
|
||||
</VideoPlayer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { streaming } from "@/consts/streaming";
|
||||
import { resolveProjectImageSrc } from "@/lib/resolveProjectImageSrc";
|
||||
import { IProject } from "@/types/IProject";
|
||||
import ArrowMoreIcon from "@/components/icons/ArrowMoreIcon";
|
||||
|
||||
export function StreamingProject({
|
||||
city,
|
||||
title,
|
||||
image,
|
||||
href,
|
||||
company,
|
||||
index,
|
||||
current,
|
||||
count,
|
||||
className,
|
||||
}: Pick<IProject, "city" | "title" | "image" | "company"> & {
|
||||
href: string;
|
||||
index: number;
|
||||
current: number;
|
||||
count: number;
|
||||
className?: string;
|
||||
}) {
|
||||
const imgSrc = resolveProjectImageSrc(image);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`lg:aspect-[344/396] aspect-[300/344] flex-1 md:max-lg:min-w-[300px] transition-[scale,transform] will-change-[transform,scale] lg:rounded-[1.111vw] rounded-2xl lg:p-[1.111vw] p-4 flex duration-500 relative overflow-hidden group max-md:absolute max-md:w-[calc(100%-40px)] select-none h-full ${
|
||||
index === current
|
||||
? "max-md:[transform:translateZ(40px)]"
|
||||
: "max-md:[scale:85%]"
|
||||
} ${
|
||||
index === (current + 1) % count
|
||||
? "max-md:[transform:translateX(calc(7.5%+20px))]"
|
||||
: index === (current - 1 + count) % count
|
||||
? "max-md:[transform:translateX(calc(-7.5%-20px))]"
|
||||
: ""
|
||||
} ${className ?? ""}`}
|
||||
>
|
||||
<div className="z-0 rounded-2xl absolute inset-0 overflow-hidden transition-transform duration-500 group-hover:scale-110">
|
||||
<img
|
||||
src={imgSrc}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="absolute inset-0 z-0 size-full object-cover object-bottom"
|
||||
/>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 z-[1] rounded-2xl bg-[linear-gradient(to_bottom,rgba(0,0,0,0.45),transparent)] lg:bg-[linear-gradient(to_top,rgba(0,0,0,0.45),transparent)]"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative z-[2] lg:self-end space-y-3 font-medium">
|
||||
<p className="heading1 font-medium">{title}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{company && (
|
||||
<div className="px-2 py-1.5 flex lg:gap-[0.278vw] gap-1 items-center lg:rounded-[1.181vw] rounded-2xl [backdrop-filter:blur(12px)] bg-[#37393B99] btns">
|
||||
<div
|
||||
className="lg:w-[0.556vw] lg:h-[0.556vw] w-2 h-2 rounded-full m-1"
|
||||
style={{ backgroundColor: company.color }}
|
||||
/>
|
||||
{company.title}
|
||||
</div>
|
||||
)}
|
||||
<p className="lg:px-[0.833vw] lg:py-[0.486vw] px-3 py-[7px] bg-[#37393B99] [backdrop-filter:blur(12px)] lg:rounded-[1.181vw] rounded-2xl btns">
|
||||
{city}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="z-[2] lg:hidden absolute right-4 bottom-4">
|
||||
<a
|
||||
className="bg-gradient btns flex gap-2 items-center px-3 py-2 font-medium rounded-xl"
|
||||
href={streaming.find((s) => s.title === title)?.url ?? href}
|
||||
>
|
||||
Смотреть
|
||||
<div className="text-white lg:size-[1.389vw] size-4">
|
||||
<ArrowMoreIcon />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href={streaming.find((s) => s.title === title)?.url ?? href}
|
||||
className="max-lg:hidden lg:group-hover:opacity-100 opacity-0 transition-opacity duration-500 absolute w-full h-full left-0 bottom-0 md:max-lg:rounded-2xl rounded-xl font-medium [backdrop-filter:blur(3px)] content-center text-center z-[3]"
|
||||
>
|
||||
<div className="btnl flex gap-2 justify-center">
|
||||
Начать демонстрацию{" "}
|
||||
<div className="text-white lg:size-[1.389vw] size-4">
|
||||
<ArrowMoreIcon />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useRefererStore } from "@/stores/useRefererStore";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function useAddReferer() {
|
||||
const { setReferer } = useRefererStore();
|
||||
|
||||
useEffect(() => {
|
||||
const referer = new URLSearchParams(window.location.search).get("ref");
|
||||
if (!referer) return;
|
||||
setReferer(referer);
|
||||
}, [setReferer]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function readBreakpoints(lg: number, md: number, sm: number) {
|
||||
if (typeof window === "undefined" || typeof window.matchMedia === "undefined") {
|
||||
return {
|
||||
isLg: false,
|
||||
isMd: false,
|
||||
isSm: false,
|
||||
isXs: false,
|
||||
};
|
||||
}
|
||||
|
||||
const lgMedia = matchMedia(`(min-width: ${lg}px)`);
|
||||
const mdMedia = matchMedia(
|
||||
`(max-width: ${lg - 1}px) and (min-width: ${md}px)`
|
||||
);
|
||||
const smMedia = matchMedia(
|
||||
`(max-width: ${md - 1}px) and (min-width: ${sm}px)`
|
||||
);
|
||||
const xsMedia = matchMedia(`(max-width: ${sm - 1}px)`);
|
||||
|
||||
return {
|
||||
isLg: lgMedia.matches,
|
||||
isMd: mdMedia.matches,
|
||||
isSm: smMedia.matches,
|
||||
isXs: xsMedia.matches,
|
||||
};
|
||||
}
|
||||
|
||||
export function useMediaQueries(lg = 1440, md = 768, sm = 640) {
|
||||
const [state, setState] = useState(() => readBreakpoints(lg, md, sm));
|
||||
|
||||
useEffect(() => {
|
||||
const lgMedia = matchMedia(`(min-width: ${lg}px)`);
|
||||
const mdMedia = matchMedia(
|
||||
`(max-width: ${lg - 1}px) and (min-width: ${md}px)`
|
||||
);
|
||||
const smMedia = matchMedia(
|
||||
`(max-width: ${md - 1}px) and (min-width: ${sm}px)`
|
||||
);
|
||||
const xsMedia = matchMedia(`(max-width: ${sm - 1}px)`);
|
||||
|
||||
const sync = () => setState(readBreakpoints(lg, md, sm));
|
||||
|
||||
lgMedia.addEventListener("change", sync);
|
||||
mdMedia.addEventListener("change", sync);
|
||||
smMedia.addEventListener("change", sync);
|
||||
xsMedia.addEventListener("change", sync);
|
||||
|
||||
sync();
|
||||
|
||||
return () => {
|
||||
lgMedia.removeEventListener("change", sync);
|
||||
mdMedia.removeEventListener("change", sync);
|
||||
smMedia.removeEventListener("change", sync);
|
||||
xsMedia.removeEventListener("change", sync);
|
||||
};
|
||||
}, [lg, md, sm]);
|
||||
|
||||
return {
|
||||
isLg: state.isLg,
|
||||
isMd: state.isMd,
|
||||
isSm: state.isSm,
|
||||
isXs: state.isXs,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
@import url("/fonts/TTHovesProAll/stylesheet.css");
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100dvh;
|
||||
min-width: 0;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "TTHovesPro", system-ui, -apple-system, "Segoe UI", Roboto,
|
||||
sans-serif;
|
||||
color: #fff;
|
||||
background-color: #0f1011;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
|
||||
.hide-scrollbars {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.hide-scrollbars::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.line1 {
|
||||
@apply 2xl:text-[128px] lg:text-[clamp(96px,96px+(100vw-1440px)/96*32,128px)] md:text-[clamp(56px,56px+(100vw-768px)/672*40,96px)] xs:text-[clamp(40px,40px+(100vw-360px)/408*16,56px)] text-[40px] leading-[85%];
|
||||
}
|
||||
|
||||
.line2 {
|
||||
@apply lg:text-[clamp(64px,4.444vw,88px)] md:text-[clamp(40px,40px+(100vw-768px)/672*24,64px)] xs:text-[clamp(32px,32px+(100vw-360px)/408*8,40px)] max-xs:text-[32px] leading-[95%];
|
||||
}
|
||||
|
||||
.heading1 {
|
||||
@apply lg:text-[clamp(28px,1.944vw,42px)] md:text-[clamp(24px,24px+(100vw-768px)/672*4,28px)] text-2xl leading-[1.167];
|
||||
}
|
||||
|
||||
.heading2 {
|
||||
@apply lg:text-[clamp(24px,1.667vw,36px)] md:text-[clamp(20px,20px+(100vw-768px)/672*4,24px)] xs:text-[clamp(16px,16px+(100vw-360px)/408*4,20px)] text-base lg:leading-[1.2] leading-[1.125];
|
||||
}
|
||||
|
||||
.accent {
|
||||
@apply lg:text-[clamp(32px,2.222vw,56px)] md:text-[clamp(24px,24px+(100vw-768px)/672*8,32px)] text-2xl lg:leading-[1.1] leading-none;
|
||||
}
|
||||
|
||||
.text1 {
|
||||
@apply lg:text-[clamp(18px,1.25vw,24px)] md:text-[clamp(14px,14px+(100vw-768px)/672*4,18px)] text-sm leading-[1.35];
|
||||
}
|
||||
|
||||
.text2 {
|
||||
@apply lg:text-[clamp(12px,0.972vw,20px)] md:text-[clamp(12px,12px+(100vw-768px)/672*2,14px)] text-xs leading-[1.35];
|
||||
}
|
||||
|
||||
.btnl {
|
||||
@apply lg:text-[clamp(18px,1.25vw,28px)] md:text-[clamp(16px,16+(100vw-768px)/256*2,18px)] text-base leading-none;
|
||||
}
|
||||
|
||||
.btnm {
|
||||
@apply lg:text-[clamp(16px,1.111vw,24px)] md:text-[clamp(14px,14px+(100vw-768px)/256*2,16px)] text-sm leading-none;
|
||||
}
|
||||
|
||||
.btns {
|
||||
@apply lg:text-[clamp(14px,0.972vw,20px)] md:text-[clamp(12px,12px+(100vw-768px)/256*2,14px)] text-xs leading-none;
|
||||
}
|
||||
|
||||
.headline1 {
|
||||
@apply font-medium text-[1.667vw] leading-[1.944vw] tracking-[-0.02em] md:max-lg:text-[3.125vw] md:max-lg:leading-[3.646vw] max-md:text-[6.667vw] max-md:leading-[7.778vw];
|
||||
}
|
||||
|
||||
.headline2 {
|
||||
@apply font-medium text-[1.389vw] leading-[1.667vw] tracking-[-0.02em] md:max-lg:text-[2.083vw] md:max-lg:leading-[2.344vw] max-md:text-base max-md:leading-[4.444vw] max-md:font-normal;
|
||||
}
|
||||
|
||||
.bg-gradient {
|
||||
background: linear-gradient(87deg, #798fff 15%, #d375ff 100%);
|
||||
}
|
||||
|
||||
.bg-gradient-saturated {
|
||||
background: linear-gradient(45deg, #7a55ff 0%, #c932e8 75%, #ff79d2 95%);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-device-width: 480px) {
|
||||
body {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import ky from "ky";
|
||||
import { getApiBase } from "@/lib/env";
|
||||
|
||||
const base = getApiBase();
|
||||
|
||||
export const api = ky.create({
|
||||
prefixUrl: base || undefined,
|
||||
credentials: "include",
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Доступ к публичным переменным окружения.
|
||||
* Поддерживаются и VITE_*, и NEXT_PUBLIC_* (см. envPrefix в vite.config.ts).
|
||||
*/
|
||||
function str(v: unknown): string {
|
||||
return typeof v === "string" ? v.trim() : "";
|
||||
}
|
||||
|
||||
export function getApiBase(): string {
|
||||
const v =
|
||||
str(import.meta.env.VITE_API_URL) || str(import.meta.env.NEXT_PUBLIC_API);
|
||||
if (!v) return "";
|
||||
return v.replace(/\/?$/, "/");
|
||||
}
|
||||
|
||||
export function hasApiConfigured(): boolean {
|
||||
return getApiBase().length > 0;
|
||||
}
|
||||
|
||||
export function getS3BucketBase(): string {
|
||||
return (
|
||||
str(import.meta.env.VITE_S3_BUCKET) ||
|
||||
str(import.meta.env.NEXT_PUBLIC_S3_BUCKET)
|
||||
);
|
||||
}
|
||||
|
||||
/** Путь из public/ (`/videos/...`) с учётом `base` в Vite */
|
||||
export function resolvePublicPath(path: string): string {
|
||||
if (
|
||||
path.startsWith("http://") ||
|
||||
path.startsWith("https://") ||
|
||||
path.startsWith("data:")
|
||||
) {
|
||||
return path;
|
||||
}
|
||||
if (!path.startsWith("/")) return path;
|
||||
const base = import.meta.env.BASE_URL;
|
||||
if (!base || base === "/") return path;
|
||||
return `${base.replace(/\/$/, "")}${path}`;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { getExampleNumber } from "libphonenumber-js";
|
||||
import examples from "libphonenumber-js/mobile/examples";
|
||||
|
||||
export const PHONE_CODE_RU = "+7";
|
||||
export const PHONE_COUNTRY_RU = "RU" as const;
|
||||
|
||||
/** Локальная часть примера номера (без кода страны) для маски */
|
||||
export function getRuMobileExampleLocal(): string | undefined {
|
||||
return getExampleNumber(PHONE_COUNTRY_RU, examples)
|
||||
?.formatInternational()
|
||||
.split(" ")
|
||||
.slice(1)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function buildRuPhoneMask(placeholderLocal: string | undefined): string {
|
||||
return `${PHONE_CODE_RU} ${(placeholderLocal?.replace(/\d/g, "9") ?? "")}`;
|
||||
}
|
||||
|
||||
/** Только цифры: 7 + до 10 цифр номера (без +) */
|
||||
export function ruPhoneDigits(stored: string): string {
|
||||
let d = stored.replace(/\D/g, "");
|
||||
if (d.startsWith("8")) d = "7" + d.slice(1);
|
||||
if (d.length === 0) return "";
|
||||
if (!d.startsWith("7")) d = "7" + d;
|
||||
return d.slice(0, 11);
|
||||
}
|
||||
|
||||
/** Отображение +7 (XXX) XXX-XX-XX без react-input-mask (совместимо с React 19) */
|
||||
export function formatRuPhoneDisplay(stored: string): string {
|
||||
const d = ruPhoneDigits(stored);
|
||||
if (!d) return "";
|
||||
const n = d.slice(1);
|
||||
if (n.length === 0) return "+7";
|
||||
|
||||
const a = n.slice(0, 3);
|
||||
const b = n.slice(3, 6);
|
||||
const c = n.slice(6, 8);
|
||||
const e = n.slice(8, 10);
|
||||
|
||||
let s = "+7 (" + a;
|
||||
if (n.length <= 3) return s;
|
||||
s += ") " + b;
|
||||
if (n.length <= 6) return s;
|
||||
s += "-" + c;
|
||||
if (n.length <= 8) return s;
|
||||
s += "-" + e;
|
||||
return s;
|
||||
}
|
||||
|
||||
export function normalizeRuPhoneFromInput(
|
||||
cleanValue: string,
|
||||
inputType: string | undefined
|
||||
): string {
|
||||
const shouldAddPhoneCode =
|
||||
inputType !== "insertFromPaste" &&
|
||||
inputType !== "insertFromDrop" &&
|
||||
inputType !== "insertCompositionText" &&
|
||||
!cleanValue.startsWith("+") &&
|
||||
!cleanValue.startsWith("7") &&
|
||||
!cleanValue.startsWith(PHONE_CODE_RU.replace("+", ""));
|
||||
|
||||
return (shouldAddPhoneCode ? PHONE_CODE_RU : "") + cleanValue;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { getS3BucketBase } from "@/lib/env";
|
||||
|
||||
/**
|
||||
* Как на основном сайте: в API может прийти уже полный URL
|
||||
* (`https://storage.yandexcloud.net/bucket/projects/uuid.jpg`) или ключ
|
||||
* (`projects/uuid.jpg`) — тогда к нему дописывается VITE_S3_BUCKET / NEXT_PUBLIC_S3_BUCKET.
|
||||
*/
|
||||
export function resolveProjectImageSrc(image: string): string {
|
||||
let src = image
|
||||
.trim()
|
||||
.replaceAll(""", '"')
|
||||
.replace(/^["']+|["']+$/g, "");
|
||||
|
||||
try {
|
||||
if (src.includes("%")) src = decodeURIComponent(src);
|
||||
} catch {
|
||||
/* оставляем как есть */
|
||||
}
|
||||
|
||||
if (src.startsWith("//")) {
|
||||
src = `https:${src}`;
|
||||
}
|
||||
if (
|
||||
src.startsWith("http://") ||
|
||||
src.startsWith("https://") ||
|
||||
src.startsWith("data:")
|
||||
) {
|
||||
return src;
|
||||
}
|
||||
|
||||
if (src.startsWith("/")) {
|
||||
const base = import.meta.env.BASE_URL;
|
||||
if (!base || base === "/") return src;
|
||||
return `${base.replace(/\/$/, "")}${src}`;
|
||||
}
|
||||
|
||||
const s3BaseRaw = getS3BucketBase();
|
||||
if (!s3BaseRaw) return src;
|
||||
const base = s3BaseRaw.replace(/\/?$/, "/");
|
||||
const path = src.replace(/^\//, "");
|
||||
return `${base}${path}`;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
import { ModalContainer } from "@/components/Layout/ModalContainer";
|
||||
import "./index.css";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
<ModalContainer />
|
||||
</QueryClientProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,48 @@
|
||||
import { api } from "@/lib/api";
|
||||
import { streaming } from "@/consts/streaming";
|
||||
import { hasApiConfigured } from "@/lib/env";
|
||||
import { IProject } from "@/types/IProject";
|
||||
import { queryKeys } from "@/queries/keys";
|
||||
import { queryOptions, useQuery } from "@tanstack/react-query";
|
||||
|
||||
const hasApi = hasApiConfigured();
|
||||
|
||||
export const queryProjectsOptions = queryOptions({
|
||||
queryKey: queryKeys.projects,
|
||||
queryFn: () => api.get("projects").json<IProject[]>(),
|
||||
enabled: hasApi,
|
||||
});
|
||||
|
||||
export function useGetProjectsQuery(tags?: string | string[]) {
|
||||
return useQuery(
|
||||
tags && tags.length > 0
|
||||
? queryOptions({
|
||||
queryKey: queryKeys.projectsWithTags(
|
||||
Array.isArray(tags) ? tags : [tags]
|
||||
),
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(
|
||||
`projects?${
|
||||
Array.isArray(tags)
|
||||
? tags.map((tag) => `tags=${tag}`).join("&")
|
||||
: "tags=" + tags
|
||||
}`
|
||||
)
|
||||
.json<IProject[]>(),
|
||||
enabled: hasApi,
|
||||
select:
|
||||
tags === "Удаленная демонстрация"
|
||||
? (data) => {
|
||||
const filtered = data.filter((p) =>
|
||||
streaming.some((s) => s.title === p.title)
|
||||
);
|
||||
if (filtered.length > 0) return filtered;
|
||||
/* Нет совпадений со списком streaming — показываем первые 3 проекта из ответа API */
|
||||
return data.slice(0, 3);
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
: queryProjectsOptions
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const queryKeys = {
|
||||
projects: ["projects"] as const,
|
||||
projectsWithTags: (tags: string[]) =>
|
||||
["projects", ...(Array.isArray(tags) ? tags : [tags])] as const,
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ReactNode } from "react";
|
||||
import { create } from "zustand";
|
||||
|
||||
interface IModalState {
|
||||
modal: ReactNode | null;
|
||||
setModal: (modal: ReactNode | null) => void;
|
||||
}
|
||||
|
||||
export const useModalStore = create<IModalState>((set) => ({
|
||||
modal: null,
|
||||
setModal: (modal) => set({ modal }),
|
||||
}));
|
||||
@@ -0,0 +1,17 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
export const useRefererStore = create<{
|
||||
referer: string | null;
|
||||
setReferer: (referer: string | null) => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
referer: null,
|
||||
setReferer: (referer) => set({ referer }),
|
||||
}),
|
||||
{
|
||||
name: "referer-stream-demo",
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
import { IProject } from "./IProject";
|
||||
|
||||
export interface ICompany {
|
||||
id: string;
|
||||
title: string;
|
||||
color: string;
|
||||
mapIcon?: string;
|
||||
logo?: string;
|
||||
projects: IProject[];
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ICompany } from "./ICompany";
|
||||
import { Product } from "./Product";
|
||||
|
||||
export type Device = "Stream" | "Touch" | "Mobile" | "VR";
|
||||
|
||||
export interface IProject {
|
||||
id: string;
|
||||
title: string;
|
||||
englishTitle: string;
|
||||
description: string;
|
||||
company?: ICompany;
|
||||
companyId?: string;
|
||||
city: string;
|
||||
englishCity: string;
|
||||
image: string;
|
||||
stage: number;
|
||||
releaseDate: string;
|
||||
tags: Product[];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export type Product =
|
||||
| "Интерактивная презентация"
|
||||
| "Удаленная демонстрация"
|
||||
| "Создание сайтов"
|
||||
| "Архитектурная визуализация"
|
||||
| "Веб-тур по 360 сферам";
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface ButtonProps {
|
||||
children: ReactNode;
|
||||
icon?: JSX.Element;
|
||||
color?: "primary" | "secondary";
|
||||
width?: "fit" | "full";
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
type?: "submit" | "reset" | "button";
|
||||
rounded?: string;
|
||||
}
|
||||
|
||||
export function Button({
|
||||
children,
|
||||
color = "primary",
|
||||
icon,
|
||||
width = "fit",
|
||||
disabled = false,
|
||||
className,
|
||||
onClick,
|
||||
type,
|
||||
rounded,
|
||||
}: ButtonProps) {
|
||||
const widthClass = width === "full" ? "w-full" : "w-fit";
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
if (type !== "submit") e.preventDefault();
|
||||
onClick?.();
|
||||
}}
|
||||
className={`group cursor-pointer relative px-6 py-2${
|
||||
rounded ? " rounded-" + rounded : ""
|
||||
} min-w-fit ${
|
||||
(color === "primary" ? "bg-gradient" : "") ||
|
||||
(color === "secondary" ? " outline-1 outline-[#3D425C]" : "")
|
||||
} ${icon ? "pr-4" : ""} flex gap-1 items-center overflow-hidden ${widthClass} ${className ?? ""} justify-between`}
|
||||
>
|
||||
<span className="group-hover:opacity-10 absolute top-0 left-0 w-full h-full transition-opacity bg-black opacity-0"></span>
|
||||
<span className={"relative font-medium" + (icon ? "" : " m-auto")}>
|
||||
{children}
|
||||
</span>
|
||||
<span className="relative">{icon}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
FieldValues,
|
||||
Path,
|
||||
useController,
|
||||
useFormContext,
|
||||
useWatch,
|
||||
} from "react-hook-form";
|
||||
|
||||
export function CheckboxesGroup<IFieldValues extends FieldValues>({
|
||||
options,
|
||||
name,
|
||||
}: {
|
||||
options: string[];
|
||||
name: Path<IFieldValues>;
|
||||
}) {
|
||||
const { control } = useFormContext<IFieldValues>();
|
||||
|
||||
const {
|
||||
field: { ref, onChange, ...inputProps },
|
||||
} = useController({ control, name });
|
||||
|
||||
const values: string[] = useWatch({ control, name });
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap lg:gap-[0.556vw] gap-2">
|
||||
{options.map((option) => (
|
||||
<label
|
||||
htmlFor={name + "_" + option}
|
||||
key={option}
|
||||
className={`cursor-pointer transition-colors lg:rounded-[1.111vw] rounded-2xl font-medium text-nowrap select-none lg:px-[1.667vw] px-6 lg:py-[1.181vw] py-[17px] btnm ${
|
||||
values.includes(option)
|
||||
? "bg-white text-black"
|
||||
: "bg-[#37393B99] hover:bg-[#37393B]"
|
||||
}`}
|
||||
>
|
||||
{option}
|
||||
<input
|
||||
id={name + "_" + option}
|
||||
className="hidden"
|
||||
type="checkbox"
|
||||
{...inputProps}
|
||||
checked={values.includes(option)}
|
||||
ref={ref}
|
||||
onChange={() => {
|
||||
onChange(
|
||||
values.includes(option)
|
||||
? values.filter((x) => x !== option)
|
||||
: [...values, option]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import CheckIcon from "@/components/icons/CheckIcon";
|
||||
|
||||
function CustomCheckbox({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (value: string, checked: boolean) => void;
|
||||
}) {
|
||||
const [checked, setChecked] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(value, checked);
|
||||
}, [checked, onChange, value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={() => setChecked(!checked)}
|
||||
className="flex gap-x-[10px] items-center hover:cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
checked ? "bg-gradient" : "bg-[#37393B]"
|
||||
} w-[20px] h-[20px] radius-[5px] rounded relative`}
|
||||
>
|
||||
{checked && (
|
||||
<div className="text-white lg:size-[1.389vw] size-4 absolute top-0">
|
||||
<CheckIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="md:text-sm">{value}</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomCheckbox;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { type Ref } from "react";
|
||||
import {
|
||||
formatRuPhoneDisplay,
|
||||
normalizeRuPhoneFromInput,
|
||||
} from "@/lib/phoneRu";
|
||||
|
||||
const inputClassName =
|
||||
"placeholder:btnl placeholder:font-medium placeholder:select-none peer btnl w-full h-full bg-transparent rounded-none transition-all outline-none";
|
||||
|
||||
interface PhoneInputRuProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onBlur?: () => void;
|
||||
inputRef?: Ref<HTMLInputElement>;
|
||||
id?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export function PhoneInputRu({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
inputRef,
|
||||
id = "tel",
|
||||
placeholder = "+7 (XXX) XXX - XX - XX",
|
||||
}: PhoneInputRuProps) {
|
||||
return (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="tel"
|
||||
autoComplete="tel"
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
className={inputClassName}
|
||||
value={formatRuPhoneDisplay(value)}
|
||||
onBlur={onBlur}
|
||||
onChange={(e) => {
|
||||
if (!e.nativeEvent.type.startsWith("input")) return;
|
||||
const cleanValue = e.target.value.replace(/\s/g, "");
|
||||
const inputType = (e.nativeEvent as InputEvent).inputType;
|
||||
onChange(normalizeRuPhoneFromInput(cleanValue, inputType));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import MuteIcon from "@/components/icons/MuteIcon";
|
||||
import UnmutedIcon from "@/components/icons/UnmutedIcon";
|
||||
|
||||
export function VideoMutingBtn({
|
||||
handleClick,
|
||||
muted,
|
||||
}: {
|
||||
muted: boolean;
|
||||
handleClick: () => void;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [point, setPoint] = useState([0, 0]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
const move = (e: MouseEvent) => setPoint([e.clientX, e.clientY]);
|
||||
el?.addEventListener("mousemove", move);
|
||||
|
||||
return () => {
|
||||
el?.removeEventListener("mousemove", move);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="absolute left-0 top-0 h-[calc(5/6*100%)] w-full z-[7]">
|
||||
<div ref={ref} className="group relative w-full h-full">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-[#37393B99] p-[1.736vw] [backdrop-filter:blur(30.72px)] rounded-full group-hover:opacity-100 transition-opacity group-hover:cursor-none opacity-0 sticky outline-none"
|
||||
style={{ left: point[0] - 32, top: point[1] - 32 }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{muted ? (
|
||||
<div className="text-white lg:size-[1.944vw] size-7">
|
||||
<UnmutedIcon />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-white lg:size-[1.944vw] size-7">
|
||||
<MuteIcon />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
ComponentProps,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { VideoMutingBtn } from "./VideoMutingBtn";
|
||||
import { VideoProgressBar } from "./VideoProgressBar";
|
||||
|
||||
export const VideoPlayer = forwardRef<
|
||||
HTMLVideoElement,
|
||||
{
|
||||
src: string;
|
||||
showMutingBtn: boolean;
|
||||
children?: React.ReactNode;
|
||||
} & ComponentProps<"video">
|
||||
>(
|
||||
(
|
||||
{ src, showMutingBtn, children, loop = true, autoPlay = true, className },
|
||||
ref
|
||||
) => {
|
||||
const progressbarRef = useRef<HTMLDivElement>(null);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useImperativeHandle(ref, () => videoRef.current!);
|
||||
|
||||
const [muted, setMuted] = useState(autoPlay);
|
||||
const [playing, setPlaying] = useState(autoPlay);
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
function handleProgressbarClick(e: React.MouseEvent) {
|
||||
const video = videoRef.current;
|
||||
const bar = progressbarRef.current;
|
||||
if (!video || !bar) return;
|
||||
video.currentTime =
|
||||
(video.duration * (e.clientX - bar.getBoundingClientRect().x)) /
|
||||
bar.clientWidth;
|
||||
setProgress(
|
||||
((video.currentTime ?? 0) / (video.duration ?? 1)) * 100
|
||||
);
|
||||
}
|
||||
|
||||
function handlePlaybackClick() {
|
||||
if (!videoRef.current) return;
|
||||
setPlaying(videoRef.current.paused);
|
||||
videoRef.current[videoRef.current.paused ? "play" : "pause"]();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
const timeUpdateHandler = () =>
|
||||
setProgress(((video.currentTime ?? 0) / (video.duration ?? 1)) * 100);
|
||||
|
||||
video.addEventListener("timeupdate", timeUpdateHandler);
|
||||
return () => video.removeEventListener("timeupdate", timeUpdateHandler);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={src}
|
||||
autoPlay={autoPlay}
|
||||
muted={muted}
|
||||
loop={loop}
|
||||
playsInline
|
||||
className={`lg:rounded-[1.111vw] rounded-2xl w-full h-full object-cover${
|
||||
className ? " " + className : ""
|
||||
}`}
|
||||
/>
|
||||
{showMutingBtn && (
|
||||
<VideoMutingBtn
|
||||
handleClick={() => setMuted(!videoRef.current!.muted)}
|
||||
muted={muted}
|
||||
/>
|
||||
)}
|
||||
<div className="absolute inset-0 rounded-2xl [background:linear-gradient(to_top,rgba(20,22,31,0.6),rgba(20,22,31,0))]" />
|
||||
<AnimatePresence>
|
||||
{muted && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<VideoProgressBar
|
||||
muted={muted}
|
||||
progress={progress}
|
||||
progressbarRef={progressbarRef}
|
||||
playing={playing}
|
||||
handlePlaybackClick={handlePlaybackClick}
|
||||
handleProgressbarClick={handleProgressbarClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
VideoPlayer.displayName = "VideoPlayer";
|
||||
@@ -0,0 +1,69 @@
|
||||
import { MouseEventHandler, RefObject, useState } from "react";
|
||||
import PauseIcon from "@/components/icons/PauseIcon";
|
||||
import PlayIcon from "@/components/icons/PlayIcon";
|
||||
|
||||
export function VideoProgressBar({
|
||||
muted,
|
||||
progressbarRef,
|
||||
playing,
|
||||
handlePlaybackClick,
|
||||
handleProgressbarClick,
|
||||
progress,
|
||||
}: {
|
||||
muted: boolean;
|
||||
progress: number;
|
||||
progressbarRef: RefObject<HTMLDivElement | null>;
|
||||
playing: boolean;
|
||||
handlePlaybackClick: MouseEventHandler<HTMLButtonElement>;
|
||||
handleProgressbarClick: MouseEventHandler<HTMLDivElement>;
|
||||
}) {
|
||||
const [isMouseDown, setIsMouseDown] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bottom-2 left-2 right-2 absolute z-10 select-none flex items-stretch gap-1 ${
|
||||
muted ? "hidden" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="p-[18px] bg-[#37393B99] rounded-2xl cursor-pointer"
|
||||
onClick={handlePlaybackClick}
|
||||
>
|
||||
{playing ? (
|
||||
<div className="text-white lg:size-[1.389vw] size-5">
|
||||
<PauseIcon />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-white lg:size-[1.389vw] size-5">
|
||||
<PlayIcon />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div
|
||||
className="flex-1 rounded-2xl bg-[#37393B99] px-6 cursor-pointer flex items-center select-none"
|
||||
onMouseDown={(e) => {
|
||||
setIsMouseDown(true);
|
||||
handleProgressbarClick(e);
|
||||
}}
|
||||
onMouseMove={(e) => {
|
||||
if (isMouseDown) handleProgressbarClick(e);
|
||||
}}
|
||||
onMouseUp={() => setIsMouseDown(false)}
|
||||
onMouseLeave={() => setIsMouseDown(false)}
|
||||
>
|
||||
<div
|
||||
ref={progressbarRef}
|
||||
className="h-1 bg-[#7A7A7A] relative rounded-3xl cursor-pointer w-full"
|
||||
>
|
||||
<div
|
||||
className="left-0 h-1 bg-white rounded-3xl transition-all"
|
||||
style={{
|
||||
width: progress + "%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_API_URL?: string;
|
||||
readonly VITE_S3_BUCKET?: string;
|
||||
readonly NEXT_PUBLIC_API?: string;
|
||||
readonly NEXT_PUBLIC_S3_BUCKET?: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
screens: {
|
||||
xs: "360px",
|
||||
sm: "640px",
|
||||
md: "768px",
|
||||
lg: "1440px",
|
||||
"2xl": "1536px",
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import path from "node:path";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
/** Подхватываем .env из корня репозитория (рядом с Next), если в подпапке своего нет */
|
||||
envDir: path.resolve(__dirname, ".."),
|
||||
/** Как в Next: можно копировать .env с NEXT_PUBLIC_API / NEXT_PUBLIC_S3_BUCKET */
|
||||
envPrefix: ["VITE_", "NEXT_PUBLIC_"],
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user