This commit is contained in:
2026-04-10 17:16:13 +05:00
commit f4b2b9b67e
80 changed files with 5776 additions and 0 deletions
+8
View File
@@ -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/
+5
View File
@@ -0,0 +1,5 @@
node_modules
dist
.env
.env.local
*.local
+597
View File
@@ -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=="],
}
}
+18
View File
@@ -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
View File
@@ -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>
+2776
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -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"
}
}
+6
View File
@@ -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.
+250
View File
@@ -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)">
&lt;link rel=&quot;preload&quot; href=&quot;TTHovesPro-DmBd.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre>
<div class="font-container" style="font-family: 'TTHovesPro-DmBd'; font-weight: 600; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</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)">
&lt;link rel=&quot;preload&quot; href=&quot;TTHovesPro-Md.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre>
<div class="font-container" style="font-family: 'TTHovesPro-Md'; font-weight: 500; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</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)">
&lt;link rel=&quot;preload&quot; href=&quot;TTHovesPro-Rg.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre>
<div class="font-container" style="font-family: 'TTHovesPro-Rg'; font-weight: normal; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</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>
+38
View File
@@ -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;
}
+4
View File
@@ -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
View File
@@ -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>
);
}
+167
View File
@@ -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>
);
}
+130
View File
@@ -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>ООО &quot;ГРАФФ.ЭСТЕЙТ&quot;</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>
);
}
+20
View File
@@ -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} />;
}
+27
View File
@@ -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);
}
+14
View File
@@ -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;
+14
View File
@@ -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;
+14
View File
@@ -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;
+12
View File
@@ -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;
+12
View File
@@ -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;
+9
View File
@@ -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;
+12
View File
@@ -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;
+14
View File
@@ -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;
+12
View File
@@ -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;
+14
View File
@@ -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;
+14
View File
@@ -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;
+165
View File
@@ -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>
);
}
+9
View File
@@ -0,0 +1,9 @@
export const modalOptions: string[] = [
"Увидели на выставке или форуме",
"Видели у других застройщиков",
"Из рейтингов и статей",
"Нашли в интернете",
"Перешли по рекламе",
"Из рассылки",
"Другое",
];
+9
View File
@@ -0,0 +1,9 @@
import type { Product } from "@/types/Product";
export const projectsTags: Product[] = [
"Интерактивная презентация",
"Удаленная демонстрация",
"Архитектурная визуализация",
"Создание сайтов",
"Веб-тур по 360 сферам",
];
+18
View File
@@ -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",
},
];
+114
View File
@@ -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">
Расскажем и покажем как это работает на&nbsp;созвоне
</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>
);
}
+18
View File
@@ -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>
);
}
+27
View File
@@ -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 модуль удаленной демонстрации доступен на&nbsp;любых
устройствах, для&nbsp;демонстрации нужен только интернет
</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>
);
}
+14
View File
@@ -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;
}
+66
View File
@@ -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,
};
}
+99
View File
@@ -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%;
}
}
+9
View File
@@ -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",
});
+40
View File
@@ -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}`;
}
+64
View File
@@ -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;
}
+42
View File
@@ -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("&quot;", '"')
.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}`;
}
+17
View File
@@ -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>
);
+48
View File
@@ -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
);
}
+5
View File
@@ -0,0 +1,5 @@
export const queryKeys = {
projects: ["projects"] as const,
projectsWithTags: (tags: string[]) =>
["projects", ...(Array.isArray(tags) ? tags : [tags])] as const,
};
+12
View File
@@ -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 }),
}));
+17
View File
@@ -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",
}
)
);
+10
View File
@@ -0,0 +1,10 @@
import { IProject } from "./IProject";
export interface ICompany {
id: string;
title: string;
color: string;
mapIcon?: string;
logo?: string;
projects: IProject[];
}
+19
View File
@@ -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[];
}
+6
View File
@@ -0,0 +1,6 @@
export type Product =
| "Интерактивная презентация"
| "Удаленная демонстрация"
| "Создание сайтов"
| "Архитектурная визуализация"
| "Веб-тур по 360 сферам";
+49
View File
@@ -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>
);
}
+56
View File
@@ -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>
);
}
+40
View File
@@ -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;
+45
View File
@@ -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));
}}
/>
);
}
+48
View File
@@ -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>
);
}
+106
View File
@@ -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";
+69
View File
@@ -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>
);
}
+12
View File
@@ -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;
}
+15
View File
@@ -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: [],
};
+23
View File
@@ -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"]
}
+16
View File
@@ -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"),
},
},
});