commit 95c9889b5d0c94b3b0d829973355484f5ecc15e2 Author: Aleksey Razorvin Date: Sun Apr 19 18:16:59 2026 +0500 Initial scaffold: Клиника УГН mobile prototype Ported HTML/CSS/JS design bundle from Claude Design to Vite + React. 20 screens (home 3 variants, booking flow, doctors, appointments, results + audiogram, recovery, audio test, chat, profile, QR, telemed, medcard, notifications). Tweaks panel with iOS/Android frames, layout/accent/font switchers. Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9faecbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.DS_Store +*.log +design.tar.gz +design_extracted diff --git a/index.html b/index.html new file mode 100644 index 0000000..30270e0 --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + Клиника УГН — мобильное приложение + + + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..25bd1f3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1677 @@ +{ + "name": "pcs-pt-mobile", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pcs-pt-mobile", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^5.4.10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "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": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e3fe231 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "pcs-pt-mobile", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^5.4.10" + } +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..6e32783 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,268 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { IOSDevice } from './frames/IOSDevice.jsx'; +import { AndroidDevice } from './frames/AndroidDevice.jsx'; +import { PhoneApp } from './PhoneApp.jsx'; + +const TWEAKS_DEFAULT = { + homeVariant: 'cards', + docVariant: 'rich', + density: 'comfort', + accent: 'teal', + font: 'inter', + layout: 'single', + screen: 'home', + device: 'ios', + scale: 'auto', + showIntro: true, +}; + +const SCALE_OPTIONS = [ + { id: 'auto', lb: 'Авто' }, + { id: '0.5', lb: '50%' }, + { id: '0.75', lb: '75%' }, + { id: '1', lb: '100%' }, +]; + +const HOME_OPTIONS = [ + { id: 'cards', lb: 'Карточки' }, + { id: 'list', lb: 'Лента' }, + { id: 'feed', lb: 'Таймлайн' }, +]; +const DOC_OPTIONS = [ + { id: 'rich', lb: 'Карточки+' }, + { id: 'list', lb: 'Список' }, + { id: 'photo', lb: 'Плитка' }, +]; +const DENSITY_OPTIONS = [ + { id: 'comfort', lb: 'Комф.' }, + { id: 'compact', lb: 'Плотно' }, +]; +const ACCENT_OPTIONS = [ + { id: 'teal', lb: 'Тил', primary: '#1F8F85', darker: '#166B63', dark: '#0F4A44', p50: '#E3F4F2', p100: '#C7E8E4', p200: '#9BD6CE', warm50: '#FDF8EE', warm100: '#F5EDDF' }, + { id: 'terra', lb: 'Терра', primary: '#C77A4C', darker: '#A65C33', dark: '#7F4426', p50: '#FBEFE4', p100: '#F5DDC9', p200: '#EBC19F', warm50: '#F4F7F3', warm100: '#E5ECE4' }, + { id: 'marine', lb: 'Марин', primary: '#3C6EA8', darker: '#23538B', dark: '#193C66', p50: '#E4EDF8', p100: '#C8DAEE', p200: '#9DBDDE', warm50: '#FBF6EE', warm100: '#F2E8D5' }, +]; +const FONT_OPTIONS = [ + { id: 'manrope', lb: 'Manrope', base: '"Manrope", system-ui, sans-serif', narrow: '"Oswald", sans-serif' }, + { id: 'inter', lb: 'Inter', base: '"Inter", system-ui, sans-serif', narrow: '"Oswald", sans-serif' }, + { id: 'golos', lb: 'Golos', base: '"Golos Text", system-ui, sans-serif', narrow: '"Oswald", sans-serif' }, +]; +const SCREEN_OPTIONS = [ + { id: 'home', lb: 'Главная' }, + { id: 'doctors', lb: 'Врачи' }, + { id: 'doctor:syndaev', lb: 'Карточка врача' }, + { id: 'booking-specs', lb: 'Запись: специализация' }, + { id: 'booking-doctor:ent', lb: 'Запись: врач' }, + { id: 'booking-time:syndaev', lb: 'Запись: время' }, + { id: 'booking-confirm:syndaev:1:16:00', lb: 'Запись: подтверждение' }, + { id: 'booking-success', lb: 'Запись: успех' }, + { id: 'appts', lb: 'Приёмы' }, + { id: 'appt:a1', lb: 'Детали приёма' }, + { id: 'results', lb: 'Результаты' }, + { id: 'result-audio', lb: 'Аудиограмма' }, + { id: 'recovery', lb: 'Восстановление' }, + { id: 'audiotest', lb: 'Тест слуха' }, + { id: 'chat', lb: 'Чат' }, + { id: 'profile', lb: 'Профиль' }, + { id: 'qr', lb: 'QR' }, + { id: 'telemed', lb: 'Телемед' }, + { id: 'medcard', lb: 'Медкарта' }, + { id: 'notifications', lb: 'Уведомления' }, +]; + +function applyTheme(tw) { + const a = ACCENT_OPTIONS.find(x => x.id === tw.accent) || ACCENT_OPTIONS[0]; + const f = FONT_OPTIONS.find(x => x.id === tw.font) || FONT_OPTIONS[0]; + const r = document.documentElement.style; + r.setProperty('--c-primary', a.primary); + r.setProperty('--c-primary-darker', a.darker); + r.setProperty('--c-primary-dark', a.dark); + r.setProperty('--c-primary-50', a.p50); + r.setProperty('--c-primary-100', a.p100); + r.setProperty('--c-primary-200', a.p200); + r.setProperty('--c-warm-50', a.warm50); + r.setProperty('--c-warm-100', a.warm100); + r.setProperty('--font-base', f.base); + r.setProperty('--font-narrow', f.narrow); + document.body.style.fontFamily = f.base; +} + +function Phone({ device = 'ios', screen, ctx, label, sublabel }) { + const content = ; + const frame = device === 'android' + ? {content} + : {content}; + if (!label) return frame; + return ( +
+ {frame} +
{label}
+ {sublabel &&
{sublabel}
} +
+ ); +} + +function TweaksPanel({ tw, setTw, onClose }) { + const group = (title, children) => ( +
+
{title}
+
{children}
+
+ ); + const opts = (options, key) => + options.map(o => ( + + )); + + return ( +
+

+ Tweaks + +

+ {group('Экран', + + )} + {group('Устройство', opts([{id:'ios',lb:'iOS'},{id:'android',lb:'Android'}], 'device'))} + {tw.layout === 'single' && group('Масштаб', opts(SCALE_OPTIONS, 'scale'))} + {group('Компоновка', opts([ + { id:'single', lb:'1 телефон' }, + { id:'home3', lb:'Главная ×3' }, + { id:'flow', lb:'Флоу записи' }, + { id:'variants', lb:'Все варианты' }, + ], 'layout'))} + {group('Главный экран', opts(HOME_OPTIONS, 'homeVariant'))} + {group('Карточки врачей', opts(DOC_OPTIONS, 'docVariant'))} + {group('Плотность', opts(DENSITY_OPTIONS, 'density'))} + {group('Цвет', + ACCENT_OPTIONS.map(a => ( + + )) + )} + {group('Шрифт', opts(FONT_OPTIONS, 'font'))} +
+ ); +} + +function FitWrap({ children, w = 402, h = 874, userScale = 'auto' }) { + const outerRef = useRef(null); + const [autoScale, setAutoScale] = useState(1); + useEffect(() => { + const outer = outerRef.current; + if (!outer) return; + const stage = outer.parentElement; + if (!stage) return; + const measure = () => { + const padding = 48; + const availW = stage.clientWidth - padding; + const availH = stage.clientHeight - padding; + const s = Math.min(availW / w, availH / h, 1); + setAutoScale(Math.max(s, 0.3)); + }; + measure(); + const ro = new ResizeObserver(measure); + ro.observe(stage); + window.addEventListener('resize', measure); + return () => { ro.disconnect(); window.removeEventListener('resize', measure); }; + }, [w, h]); + const scale = userScale === 'auto' ? autoScale : parseFloat(userScale); + return ( +
+
{children}
+
+ ); +} + +export default function App() { + const [tw, setTw] = useState(TWEAKS_DEFAULT); + const [panelOpen, setPanelOpen] = useState(true); + const [introVisible, setIntroVisible] = useState(tw.showIntro !== false); + + useEffect(() => { applyTheme(tw); }, [tw.accent, tw.font]); + + const ctx = { homeVariant: tw.homeVariant, docVariant: tw.docVariant, density: tw.density }; + + const content = useMemo(() => { + if (tw.layout === 'home3') { + return ( +
+ + + +
+ ); + } + if (tw.layout === 'flow') { + return ( +
+ + + + + +
+ ); + } + if (tw.layout === 'variants') { + return ( +
+ + + + + + + + + + + + + + + + +
+ ); + } + return ( + + + + ); + }, [tw, ctx.homeVariant, ctx.docVariant, ctx.density]); + + const stageClass = tw.layout === 'single' ? 'stage' : 'stage grid-mode'; + + return ( +
+
+ {content} +
+ + {introVisible && ( +
+ 📱 Клиника УГН · мобильный прототип · откройте Tweaks справа, чтобы переключать экраны и варианты. + +
+ )} + + {panelOpen && setPanelOpen(false)} />} + {!panelOpen && ( + + )} +
+ ); +} diff --git a/src/PhoneApp.jsx b/src/PhoneApp.jsx new file mode 100644 index 0000000..26d418e --- /dev/null +++ b/src/PhoneApp.jsx @@ -0,0 +1,82 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { TabBar } from './components.jsx'; +import { HomeCardsScreen, HomeListScreen, HomeFeedScreen } from './screens/screens-home.jsx'; +import { + BookingSpecsScreen, BookingDoctorScreen, BookingTimeScreen, + BookingConfirmScreen, BookingSuccessScreen, + DoctorsTabScreen, DoctorDetailScreen, +} from './screens/screens-booking.jsx'; +import { + ApptsTabScreen, ApptDetailScreen, + ResultsScreen, ResultAudioScreen, + RecoveryScreen, AudioTestScreen, + ChatTabScreen, ProfileTabScreen, QRScreen, + TelemedScreen, MedcardScreen, NotificationsScreen, +} from './screens/screens-misc.jsx'; + +function renderScreen(screenId, nav, ctx) { + const parts = screenId.split(':'); + const id = parts[0]; + const HOME = { cards: HomeCardsScreen, list: HomeListScreen, feed: HomeFeedScreen }[ctx.homeVariant] || HomeCardsScreen; + switch (id) { + case 'home': return ; + case 'doctors': return ; + case 'doctor': return ; + case 'booking-specs': return ; + case 'booking-doctor': return ; + case 'booking-time': return ; + case 'booking-confirm': return ; + case 'booking-success': return ; + case 'appts': return ; + case 'appt': return ; + case 'results': return ; + case 'result-audio': return ; + case 'recovery': return ; + case 'audiotest': return ; + case 'chat': return ; + case 'profile': return ; + case 'qr': return ; + case 'telemed': return ; + case 'medcard': return ; + case 'notifications': return ; + default: return
Экран не найден: {screenId}
; + } +} + +const TAB_IDS = ['home', 'appts', 'doctors', 'chat', 'profile']; + +export function PhoneApp({ initialScreen, ctx }) { + const [stack, setStack] = useState([initialScreen]); + + useEffect(() => { setStack([initialScreen]); }, [initialScreen]); + + const nav = useMemo(() => ({ + push: (id) => setStack(s => [...s, id]), + pop: () => setStack(s => s.length > 1 ? s.slice(0, -1) : s), + set: (id) => setStack([id]), + reset:() => setStack(['home']), + }), []); + + const current = stack[stack.length - 1]; + const tabId = TAB_IDS.includes(current.split(':')[0]) ? current.split(':')[0] : null; + const showTabBar = tabId !== null; + + const modalScreens = ['qr', 'telemed', 'booking-success', 'audiotest']; + const isModal = modalScreens.includes(current.split(':')[0]); + + return ( +
+
+ {renderScreen(current, nav, ctx)} +
+ {showTabBar && !isModal && ( + nav.set(t)} /> + )} +
+ ); +} diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..73501d0 --- /dev/null +++ b/src/app.css @@ -0,0 +1,347 @@ +/* ============================================================ + Клиника УГН — Мобильное приложение + ============================================================ */ + +:root { + --c-primary: #2BB4A8; + --c-primary-dark: #1F8F85; + --c-primary-darker: #166B63; + --c-primary-50: #F2FAF9; + --c-primary-100: #E3F4F2; + --c-primary-200: #C7E9E4; + --c-primary-300: #9ED8D1; + + --c-accent: #E04E44; + --c-accent-dark: #B63D35; + --c-accent-50: #FCF1F0; + + --c-warm-50: #FBF7EE; + --c-warm-100: #F5EDDF; + --c-warm-200: #E8DCC5; + --c-warm-text: #7A6A2E; + + --c-success: #2E9B6B; + --c-success-50: #E8F5EE; + --c-warning: #E8A13C; + --c-warning-50: #FBEFD8; + --c-danger: #D94141; + + --c-bg: #F7F9FB; + --c-bg-card: #FFFFFF; + --c-bg-app-ios: #F5F7F9; + --c-bg-app-and: #F4FBF8; + + --c-border: #EAF0F3; + --c-border-strong: #D8E1E6; + --c-divider: #F0F4F6; + + --c-fg-1: #17242E; + --c-fg-2: #3E4C5D; + --c-fg-3: #6B7A89; + --c-fg-4: #9AA7B4; + + --font-base: 'Manrope', system-ui, sans-serif; + --font-narrow: 'Oswald', sans-serif; + + --r-sm: 8px; + --r-md: 12px; + --r-lg: 16px; + --r-xl: 20px; + --r-2xl: 24px; + + --sh-sm: 0 1px 2px rgba(15, 42, 55, 0.04), 0 2px 6px rgba(15, 42, 55, 0.04); + --sh-md: 0 2px 8px rgba(15, 42, 55, 0.06), 0 8px 24px rgba(15, 42, 55, 0.06); + --sh-lg: 0 4px 16px rgba(15, 42, 55, 0.08), 0 16px 40px rgba(15, 42, 55, 0.08); +} + +[data-density="compact"] { + --pad-card: 12px; + --pad-row: 10px 14px; + --gap-xs: 8px; + --gap-sm: 10px; + --gap-md: 14px; + --gap-lg: 18px; +} +[data-density="spacious"] { + --pad-card: 18px; + --pad-row: 16px 18px; + --gap-xs: 12px; + --gap-sm: 16px; + --gap-md: 22px; + --gap-lg: 28px; +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; padding: 0; + font-family: var(--font-base); + background: #EBEEF2; + color: var(--c-fg-1); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +body { overflow: hidden; } +#root { width: 100vw; height: 100vh; } + +button { font-family: inherit; border: 0; background: none; cursor: pointer; padding: 0; } +input, select, textarea { font-family: inherit; } + +.noscroll::-webkit-scrollbar { display: none; } +.noscroll { scrollbar-width: none; } + +.seg { + display: inline-flex; background: #EEF2F5; border-radius: 10px; padding: 3px; + gap: 2px; +} +.seg button { + padding: 7px 14px; font-size: 13px; font-weight: 700; color: var(--c-fg-3); + border-radius: 8px; transition: all .2s; +} +.seg button.on { background: #fff; color: var(--c-primary-darker); box-shadow: var(--sh-sm); } + +.btn-p { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + background: var(--c-primary); color: #fff; font-weight: 700; font-size: 15px; + border-radius: 12px; padding: 14px 20px; transition: background .15s; +} +.btn-p:hover { background: var(--c-primary-dark); } +.btn-p.block { width: 100%; } +.btn-accent { background: var(--c-accent); } +.btn-accent:hover { background: var(--c-accent-dark); } + +.btn-s { + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + background: var(--c-primary-100); color: var(--c-primary-darker); font-weight: 700; + font-size: 14px; border-radius: 10px; padding: 10px 14px; +} +.btn-s:hover { background: var(--c-primary-200); } + +.btn-g { + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + background: #fff; color: var(--c-fg-2); font-weight: 600; + font-size: 14px; border-radius: 10px; padding: 10px 14px; + border: 1px solid var(--c-border); +} + +.chip { + display: inline-flex; align-items: center; gap: 6px; + padding: 5px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; + background: var(--c-primary-100); color: var(--c-primary-darker); +} +.chip-warm { background: var(--c-warm-100); color: var(--c-warm-text); } +.chip-soft { background: var(--c-bg); color: var(--c-fg-3); border: 1px solid var(--c-border); font-weight: 500; } +.chip-success { background: var(--c-success-50); color: var(--c-success); } +.chip-danger { background: var(--c-accent-50); color: var(--c-accent); } + +.card { + background: #fff; border-radius: var(--r-lg); padding: var(--pad-card, 16px); + box-shadow: var(--sh-sm); +} + +.h-screen { font-size: 28px; font-weight: 700; letter-spacing: -0.3px; color: var(--c-fg-1); margin: 0; line-height: 1.15; } +.h-sec { font-size: 17px; font-weight: 700; color: var(--c-fg-1); margin: 0; } +.h-row { font-size: 15px; font-weight: 700; color: var(--c-fg-1); margin: 0; } +.sub { font-size: 13px; color: var(--c-fg-3); } +.mute { color: var(--c-fg-3); } +.price { font-family: var(--font-narrow); font-weight: 700; color: var(--c-fg-1); } + +.avatar { + display: inline-flex; align-items: center; justify-content: center; + font-weight: 700; color: var(--c-primary-darker); + background: linear-gradient(135deg, #E3F4F2, #B5E3DE); + border-radius: 50%; flex-shrink: 0; +} + +.dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .5; display: inline-block; vertical-align: middle; } + +.tabbar { + position: absolute; left: 0; right: 0; bottom: 0; + background: rgba(255,255,255,0.92); + backdrop-filter: blur(18px) saturate(180%); + -webkit-backdrop-filter: blur(18px) saturate(180%); + border-top: 1px solid var(--c-border); + padding: 8px 4px 22px; + display: flex; justify-content: space-around; align-items: flex-start; + z-index: 40; +} +.tab { + flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px; + font-size: 10px; font-weight: 600; color: var(--c-fg-4); padding: 4px 2px; + transition: color .15s; +} +.tab.on { color: var(--c-primary-darker); } + +.pills { display: flex; gap: 8px; overflow-x: auto; padding: 2px 16px; } +.pills::-webkit-scrollbar { display: none; } +.pill { + flex-shrink: 0; padding: 8px 14px; border-radius: 999px; font-size: 13px; + font-weight: 600; background: #fff; color: var(--c-fg-2); border: 1px solid var(--c-border); +} +.pill.on { background: var(--c-primary-darker); color: #fff; border-color: var(--c-primary-darker); } + +.press { transition: transform .12s ease, opacity .12s ease; } +.press:active { transform: scale(0.98); opacity: .9; } + +.divider { height: 1px; background: var(--c-divider); margin: 0; border: 0; } + +.stat-big { + font-family: var(--font-narrow); font-weight: 700; font-size: 28px; color: var(--c-primary-darker); + line-height: 1; +} + +/* Stage – centers a single phone OR grid of phones */ +.stage { + width: 100%; + height: 100%; + overflow: auto; + display: flex; + align-items: center; + justify-content: center; + padding: 28px; + box-sizing: border-box; + background: + radial-gradient(1200px 600px at 20% 0%, #F2EADA 0%, transparent 60%), + radial-gradient(1000px 500px at 90% 100%, #E3F1EE 0%, transparent 50%), + #EDEFF3; +} +.stage.grid-mode { align-items: flex-start; padding-top: 28px; padding-bottom: 40px; overflow: auto; } + +.phones-grid { + display: grid; + grid-template-columns: repeat(auto-fit, 410px); + gap: 32px 28px; + justify-content: center; +} +.phone-cell { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} +.phone-cell .label { + font-size: 13px; + color: #4A5560; + font-weight: 700; + letter-spacing: .2px; +} +.phone-cell .sublabel { + font-size: 11px; + color: #8A95A2; +} + +/* Tweaks panel */ +.tweaks-panel { + position: fixed; + right: 18px; + bottom: 18px; + width: 300px; + max-height: 80vh; + overflow-y: auto; + background: #fff; + border-radius: 20px; + box-shadow: 0 20px 60px rgba(15,30,40,0.25), 0 4px 16px rgba(15,30,40,0.08); + padding: 16px; + z-index: 100; + font-size: 13px; +} +.tweaks-panel h3 { + margin: 0 0 12px; + font-size: 14px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: space-between; +} +.tweaks-panel h3 button { + background: transparent; + border: 0; + color: #8A95A2; + cursor: pointer; + font-size: 18px; + padding: 0; +} +.tweaks-section { margin-bottom: 14px; } +.tweaks-section .label { + font-size: 11px; + text-transform: uppercase; + letter-spacing: .6px; + color: #8A95A2; + font-weight: 700; + margin-bottom: 6px; +} +.tweaks-options { + display: flex; + flex-wrap: wrap; + gap: 5px; +} +.tweaks-options button { + padding: 6px 10px; + border-radius: 8px; + border: 1px solid #E4EAF2; + background: #fff; + font-size: 12px; + font-weight: 600; + color: #4A5560; + cursor: pointer; +} +.tweaks-options button.on { + background: var(--c-primary-darker); + color: #fff; + border-color: var(--c-primary-darker); +} +.tweaks-options select { + flex: 1; + padding: 7px 10px; + border-radius: 8px; + border: 1px solid #E4EAF2; + font-size: 12px; + font-weight: 600; + background: #fff; +} + +.tweaks-fab { + position: fixed; + right: 18px; + bottom: 18px; + width: 44px; + height: 44px; + border-radius: 999px; + background: #fff; + box-shadow: 0 4px 16px rgba(15,30,40,0.15); + border: 0; + cursor: pointer; + z-index: 99; + display: flex; align-items: center; justify-content: center; + font-size: 20px; +} + +.intro-banner { + position: fixed; + left: 50%; + top: 18px; + transform: translateX(-50%); + background: #fff; + border-radius: 14px; + padding: 10px 16px; + box-shadow: 0 8px 24px rgba(15,30,40,0.12); + font-size: 13px; + color: #4A5560; + z-index: 50; + display: flex; + align-items: center; + gap: 10px; + max-width: 90vw; +} +.intro-banner b { color: var(--c-primary-darker); } +.intro-banner button { + background: transparent; + border: 0; + cursor: pointer; + color: #8A95A2; + font-size: 18px; + padding: 0 0 0 6px; +} + +@keyframes pulse { 0%{opacity:.5;transform:scale(.95)} 100%{opacity:0;transform:scale(1.15)} } +@keyframes blink { 50% { opacity: .3 } } diff --git a/src/components.jsx b/src/components.jsx new file mode 100644 index 0000000..1ca6f55 --- /dev/null +++ b/src/components.jsx @@ -0,0 +1,222 @@ +import React from 'react'; +import { I } from './icons.jsx'; + +export function Avatar({ init, size = 44, style = {} }) { + return ( +
+ {init} +
+ ); +} + +export function DoctorCard({ doc, variant = 'rich', onClick, dense }) { + if (variant === 'list') { + return ( + + ); + } + if (variant === 'rich') { + return ( + + ); + } + return ( + + ); +} + +export function AppointmentCard({ appt, doctor, addr, onClick, compact = false }) { + const isUpcoming = appt.status === 'upcoming'; + return ( + + ); +} + +export function SectionHeader({ title, action, onAction, pad = '0 20px' }) { + return ( +
+

{title}

+ {action && } +
+ ); +} + +export function TabBar({ active, onChange, platform = 'ios' }) { + const tabs = [ + { id: 'home', label: 'Главная', icon: I.home }, + { id: 'appts', label: 'Приёмы', icon: I.calendar }, + { id: 'doctors', label: 'Врачи', icon: I.stetho }, + { id: 'chat', label: 'Чат', icon: I.chat, badge: 2 }, + { id: 'profile', label: 'Профиль', icon: I.profile }, + ]; + return ( +
+ {tabs.map(t => { + const IconC = t.icon; + const on = active === t.id; + return ( + + ); + })} +
+ ); +} + +export function ScreenHeader({ title, subtitle, onBack, rightIcon, onRight, center = false }) { + return ( +
+ {onBack && ( + + )} +
+ {subtitle &&
{subtitle}
} +
{title}
+
+ {rightIcon && ( + + )} +
+ ); +} diff --git a/src/data.js b/src/data.js new file mode 100644 index 0000000..e8e7fec --- /dev/null +++ b/src/data.js @@ -0,0 +1,88 @@ +// Реальные данные с сайта Клиники УГН им. проф. Е.Н. Оленевой +// (oclinica.ru/lor) — врачи, услуги, цены. + +export const CLINIC_DATA = { + clinic: { + name: 'Клиника УГН', + full: 'Клиника ухо, горло, нос им. проф. Е.Н. Оленевой', + phone: '(342) 207-03-03', + hours: '9:00–21:00 ежедневно', + addresses: [ + { id: 'tsetkin', short: 'К. Цеткин, 9', full: 'ул. Клары Цеткин, 9', note: 'Основная клиника + Центр сурдологии' }, + { id: 'zvezda', short: 'Газеты Звезда, 31а', full: 'ул. Газеты Звезда, 31а', note: 'Клиника лечения кашля и аллергии' }, + { id: 'krasnokamsk', short: 'Краснокамск', full: 'г. Краснокамск, филиал', note: 'Филиал' }, + ], + }, + doctors: [ + { id: 'makarova', init: 'МЛ', name: 'Макарова Людмила Германовна', spec: 'ЛОР-врач · Сурдолог', exp: 24, price: 1800, rating: 4.9, reviews: 312, kmn: false, next: 'Завтра, 10:30', address: 'tsetkin' }, + { id: 'semerikova',init: 'СН', name: 'Семерикова Наталия Александровна', spec: 'ЛОР-хирург · Сурдолог', exp: 28, price: 2400, rating: 5.0, reviews: 428, kmn: true, next: 'Сегодня, 16:00', address: 'tsetkin' }, + { id: 'voronchikhina', init: 'ВН', name: 'Ворончихина Наталия Валерьевна', spec: 'Отоневролог · Хирург', exp: 22, price: 2400, rating: 4.9, reviews: 201, kmn: true, next: '21 апр, 09:00', address: 'tsetkin' }, + { id: 'lobanova', init: 'ЛИ', name: 'Лобанова Ирина Юрьевна', spec: 'ЛОР-врач · Сурдолог', exp: 18, price: 1800, rating: 4.8, reviews: 256, kmn: false, next: 'Завтра, 14:15', address: 'tsetkin' }, + { id: 'torsunova', init: 'ТН', name: 'Торсунова Наталья Сергеевна', spec: 'Слухопротезирование', exp: 14, price: 1500, rating: 4.9, reviews: 98, kmn: false, next: '22 апр, 11:00', address: 'tsetkin' }, + { id: 'suvorova', init: 'СС', name: 'Суворова Светлана Викторовна', spec: 'ЛОР-врач · Сурдолог', exp: 16, price: 1800, rating: 4.8, reviews: 174, kmn: false, next: 'Сегодня, 18:30', address: 'zvezda' }, + { id: 'syndaev', init: 'СА', name: 'Синдяев Андрей Викторович', spec: 'ЛОР-хирург', exp: 21, price: 2200, rating: 5.0, reviews: 389, kmn: false, next: 'Завтра, 12:00', address: 'tsetkin' }, + { id: 'zykin', init: 'ЗО', name: 'Зыкин Олег Владимирович', spec: 'ЛОР-врач · Фониатр', exp: 19, price: 2000, rating: 4.9, reviews: 217, kmn: false, next: '21 апр, 15:30', address: 'tsetkin' }, + ], + services: [ + { cat: 'Приёмы', name: 'Приём ЛОР-врача первичный', price: 1800 }, + { cat: 'Приёмы', name: 'Приём ЛОР-врача повторный', price: 1400 }, + { cat: 'Приёмы', name: 'Консультация кандидата мед. наук', price: 2400 }, + { cat: 'Приёмы', name: 'Приём детского ЛОР-врача', price: 1800 }, + { cat: 'Диагностика',name: 'Эндоскопия ЛОР-органов', price: 1200 }, + { cat: 'Диагностика',name: 'Аудиометрия', price: 1100 }, + { cat: 'Диагностика',name: 'Тимпанометрия', price: 800 }, + { cat: 'Процедуры', name: 'Промывание миндалин', price: 1200 }, + { cat: 'Процедуры', name: 'Промывание носа «Кукушка»', price: 900 }, + { cat: 'Операции', name: 'Аденотомия (эндоскопическая)', price: 28000 }, + { cat: 'Операции', name: 'Вазотомия нижних раковин', price: 22000 }, + { cat: 'Операции', name: 'Септопластика', price: 45000 }, + { cat: 'Операции', name: 'Тонзиллэктомия', price: 38000 }, + ], + specializations: [ + { id: 'lor', icon: 'ear', label: 'ЛОР', count: 12 }, + { id: 'surdo', icon: 'hear', label: 'Сурдология', count: 5 }, + { id: 'allergo', icon: 'leaf', label: 'Аллергология', count: 3 }, + { id: 'phono', icon: 'mic', label: 'Фониатрия', count: 2 }, + { id: 'kids', icon: 'baby', label: 'Детский ЛОР', count: 7 }, + { id: 'surgery', icon: 'scalpel', label: 'Хирургия', count: 6 }, + ], + appointments: [ + { id: 'a1', status: 'upcoming', doctor: 'semerikova', date: '21 апр', weekday: 'понедельник', time: '16:00', room: 'Каб. 204', address: 'tsetkin', type: 'Первичный приём' }, + { id: 'a2', status: 'upcoming', doctor: 'torsunova', date: '25 апр', weekday: 'пятница', time: '11:00', room: 'Каб. 118', address: 'tsetkin', type: 'Аудиометрия' }, + { id: 'a3', status: 'past', doctor: 'makarova', date: '8 апр', weekday: 'среда', time: '10:30', room: 'Каб. 202', address: 'tsetkin', type: 'Первичный приём', hasReport: true }, + { id: 'a4', status: 'past', doctor: 'zykin', date: '28 мар', weekday: 'пятница', time: '15:00', room: 'Каб. 210', address: 'tsetkin', type: 'Консультация', hasReport: true }, + ], + results: [ + { id: 'r1', name: 'Аудиограмма', date: '8 апр 2026', doctor: 'makarova', status: 'ready', kind: 'audio' }, + { id: 'r2', name: 'Эндоскопия носоглотки', date: '8 апр 2026', doctor: 'makarova', status: 'ready', kind: 'image' }, + { id: 'r3', name: 'Общий анализ крови', date: '1 апр 2026', doctor: 'syndaev', status: 'ready', kind: 'lab' }, + { id: 'r4', name: 'Мазок из зева', date: '28 мар 2026',doctor: 'zykin', status: 'ready', kind: 'lab' }, + { id: 'r5', name: 'Посев на микрофлору', date: '20 апр 2026',doctor: 'syndaev', status: 'pending', kind: 'lab' }, + ], + articles: [ + { tag: 'Дети', title: 'Как понять, что у ребёнка отит', mins: 4, author: 'Макарова Л.Г.' }, + { tag: 'Операции', title: 'Восстановление после септопластики', mins: 6, author: 'Синдяев А.В.' }, + { tag: 'Беременность', title: 'Безопасно лечим горло при беременности', mins: 5, author: 'Лобанова И.Ю.' }, + { tag: 'Слух', title: 'Когда пора проверить слух', mins: 3, author: 'Семерикова Н.А.' }, + ], + recovery: { + op: 'Септопластика', + surgeon: 'syndaev', + date: '12 апр 2026', + dayNow: 6, + totalDays: 14, + steps: [ + { day: 0, title: 'День операции', done: true, note: 'Постельный режим, холод на переносицу' }, + { day: 1, title: '1 день — снятие тампонов',done: true, note: 'Контрольный осмотр хирурга' }, + { day: 3, title: '3 день — промывание', done: true, note: 'Солевой раствор 4 раза в день' }, + { day: 6, title: '6 день — осмотр', done: false, active: true, note: 'Осмотр хирурга, снятие корочек' }, + { day: 10, title: '10 день — контроль', done: false, note: 'Эндоскопия полости носа' }, + { day: 14, title: 'Выписка', done: false, note: 'Финальный осмотр' }, + ], + meds: [ + { name: 'Аква Марис', freq: '4 раза в день', nextTake: '14:00', taken: 2, total: 4 }, + { name: 'Амоксиклав 625 мг', freq: '2 раза в день', nextTake: '20:00', taken: 1, total: 2 }, + { name: 'Нурофен', freq: 'при боли', nextTake: '—', taken: 0, total: 0 }, + ], + }, +}; diff --git a/src/frames/AndroidDevice.jsx b/src/frames/AndroidDevice.jsx new file mode 100644 index 0000000..aee90f1 --- /dev/null +++ b/src/frames/AndroidDevice.jsx @@ -0,0 +1,74 @@ +import React from 'react'; + +const MD_C = { + surface: '#f4fbf8', + onSurface: '#171d1b', + frameBorder: 'rgba(116,119,117,0.5)', +}; + +function AndroidStatusBar({ dark = false }) { + const c = dark ? '#fff' : MD_C.onSurface; + return ( +
+
+ 9:30 +
+
+
+
+ + + + + + +
+ + + + +
+
+ ); +} + +function AndroidNavBar({ dark = false }) { + return ( +
+
+
+ ); +} + +export function AndroidDevice({ children, width = 412, height = 892, dark = false }) { + return ( +
+ +
+ {children} +
+ +
+ ); +} diff --git a/src/frames/IOSDevice.jsx b/src/frames/IOSDevice.jsx new file mode 100644 index 0000000..7aa8723 --- /dev/null +++ b/src/frames/IOSDevice.jsx @@ -0,0 +1,70 @@ +import React from 'react'; + +export function IOSStatusBar({ dark = false, time = '9:41' }) { + const c = dark ? '#fff' : '#000'; + return ( +
+
+ {time} +
+
+ + + + + + + + + + + + + + + + +
+
+ ); +} + +export function IOSDevice({ children, width = 402, height = 874, dark = false }) { + return ( +
+
+
+ +
+
+
{children}
+
+
+
+
+
+ ); +} diff --git a/src/icons.jsx b/src/icons.jsx new file mode 100644 index 0000000..f989b95 --- /dev/null +++ b/src/icons.jsx @@ -0,0 +1,45 @@ +// Outline icons — 24x24, 1.75 stroke. Нейтральный медицинский стиль. +import React from 'react'; + +const Icon = ({ d, size = 22, stroke = 'currentColor', fill = 'none', sw = 1.75, children, style }) => ( + + {d ? : children} + +); + +export const I = { + home: (p) => , + calendar: (p) => , + chat: (p) => , + profile: (p) => , + phone: (p) => , + pin: (p) => , + clock: (p) => , + search: (p) => , + chev: (p) => , + chevL: (p) => , + chevD: (p) => , + close: (p) => , + check: (p) => , + bell: (p) => , + video: (p) => , + doc: (p) => , + plus: (p) => , + heart: (p) => , + star: (p) => , + ear: (p) => , + hearing: (p) => , + stetho: (p) => , + pill: (p) => , + qr: (p) => , + card: (p) => , + file: (p) => , + filter: (p) => , + mic: (p) => , + user: (p) => , + shield: (p) => , + gift: (p) => , + volume: (p) => , + arrow: (p) => , + menu: (p) => , +}; diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..4dc7be2 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App.jsx'; +import './tokens.css'; +import './app.css'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +); diff --git a/src/screens/screens-booking.jsx b/src/screens/screens-booking.jsx new file mode 100644 index 0000000..a20e53b --- /dev/null +++ b/src/screens/screens-booking.jsx @@ -0,0 +1,450 @@ +import React, { useState } from 'react'; +import { I } from '../icons.jsx'; +import { CLINIC_DATA } from '../data.js'; +import { Avatar, DoctorCard, ScreenHeader } from '../components.jsx'; + +export function BookingSpecsScreen({ nav }) { + const { specializations } = CLINIC_DATA; + return ( +
+ nav.pop()} /> +
+
Выберите направление
+
27 ЛОР-врачей в клинике · 6 кандидатов мед. наук
+ +
+ {['Взрослому', 'Ребёнку', 'Онлайн'].map((l, i) => ( + + ))} +
+ +
+ {specializations.map(s => { + const icons = { ear: I.ear, hear: I.hearing, leaf: I.heart, mic: I.mic, baby: I.profile, scalpel: I.stetho }; + const Ic = icons[s.icon]; + return ( + + ); + })} +
+
+
+ ); +} + +export function BookingDoctorScreen({ nav }) { + const { doctors } = CLINIC_DATA; + const [q, setQ] = useState(''); + const filtered = doctors.filter(d => !q || d.name.toLowerCase().includes(q.toLowerCase()) || d.spec.toLowerCase().includes(q.toLowerCase())); + return ( +
+ nav.pop()} rightIcon={I.filter} /> +
+
+ + setQ(e.target.value)} placeholder="ФИО или специализация" style={{ + flex: 1, border: 0, outline: 0, fontSize: 15, background: 'transparent', + }} /> +
+
+
+ {['Все','Свободно сегодня','Кандидаты наук','Хирурги','Детские'].map((p,i)=>( + + ))} +
+
+ {filtered.map(d => ( + nav.push('booking-time:' + d.id)} /> + ))} +
+
+ ); +} + +export function BookingTimeScreen({ nav, doctorId }) { + const doc = CLINIC_DATA.doctors.find(d => d.id === doctorId); + const [selDate, setSelDate] = useState(1); + const [selTime, setSelTime] = useState('16:00'); + + const dates = [ + { i: 0, d: 'Сегодня', n: '20', wd: 'вс' }, + { i: 1, d: 'Пн', n: '21', wd: 'пн' }, + { i: 2, d: 'Вт', n: '22', wd: 'вт' }, + { i: 3, d: 'Ср', n: '23', wd: 'ср' }, + { i: 4, d: 'Чт', n: '24', wd: 'чт' }, + { i: 5, d: 'Пт', n: '25', wd: 'пт' }, + { i: 6, d: 'Сб', n: '26', wd: 'сб' }, + ]; + const slots = { + morning: ['09:00','09:30','10:00','10:30','11:15','11:45'], + day: ['12:00','13:30','14:00','14:30','15:15','15:45'], + evening: ['16:00','16:30','17:15','18:00','18:30','19:15','20:00'], + }; + const booked = new Set(['10:30','14:00','18:00']); + + return ( +
+ nav.pop()} /> + +
+
+ +
+
{doc.name.split(' ').slice(0,2).join(' ')}
+
{doc.spec}
+
+
{doc.price} ₽
+
+
+ +
+
+

Апрель 2026

+ +
+
+ {dates.map(d => ( + + ))} +
+
+ +
+ {Object.entries({ 'Утро': slots.morning, 'День': slots.day, 'Вечер': slots.evening }).map(([label, arr]) => ( +
+
{label}
+
+ {arr.map(t => { + const isBooked = booked.has(t); + const sel = selTime === t; + return ( + + ); + })} +
+
+ ))} +
+ +
+ +
+
+ ); +} + +export function BookingConfirmScreen({ nav, doctorId, dateIdx, time }) { + const doc = CLINIC_DATA.doctors.find(d => d.id === doctorId); + const addr = CLINIC_DATA.clinic.addresses.find(a => a.id === doc.address); + const [type, setType] = useState('offline'); + const [comment, setComment] = useState(''); + const dates = ['Сегодня, 20 апр','Пн, 21 апр','Вт, 22 апр','Ср, 23 апр','Чт, 24 апр','Пт, 25 апр','Сб, 26 апр']; + return ( +
+ nav.pop()} /> +
+
+
+ +
+
{doc.name.split(' ').slice(0,2).join(' ')}
+
{doc.spec}
+
+
+
+
+ Дата + {dates[dateIdx]} +
+
+
+ Время + {time} +
+
+
+ Адрес + {addr.full} +
+
+ +
Формат приёма
+
+ {[ + { id: 'offline', lb: 'Очно', sub: 'В клинике', i: I.pin }, + { id: 'online', lb: 'Онлайн', sub: 'Видеосвязь', i: I.video }, + ].map(o => { + const OIcon = o.i; + return ( + + ); + })} +
+ +
Комментарий для врача
+