Самир начал работу

This commit is contained in:
2026-09-06 12:27:07 +03:00
parent debeada468
commit e920ee99f6
4 changed files with 590 additions and 228 deletions

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
sex.md
notepad2.md

243
package-lock.json generated
View File

@ -1627,9 +1627,9 @@
} }
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -1665,19 +1665,36 @@
} }
}, },
"node_modules/@oxc-project/types": { "node_modules/@oxc-project/types": {
"version": "0.144.0", "version": "0.148.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz",
"integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/Boshen" "url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz",
"integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
} }
}, },
"node_modules/@rolldown/binding-android-arm64": { "node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz",
"integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1692,9 +1709,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-arm64": { "node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz",
"integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1709,9 +1726,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-x64": { "node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz",
"integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1726,9 +1743,9 @@
} }
}, },
"node_modules/@rolldown/binding-freebsd-x64": { "node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz",
"integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1743,9 +1760,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm-gnueabihf": { "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz",
"integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -1760,9 +1777,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-gnu": { "node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz",
"integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1780,9 +1797,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-musl": { "node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz",
"integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1800,9 +1817,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-ppc64-gnu": { "node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz",
"integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -1820,9 +1837,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-s390x-gnu": { "node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz",
"integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -1840,9 +1857,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-gnu": { "node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz",
"integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1860,9 +1877,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-musl": { "node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz",
"integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1880,9 +1897,9 @@
} }
}, },
"node_modules/@rolldown/binding-openharmony-arm64": { "node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz",
"integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1897,9 +1914,9 @@
} }
}, },
"node_modules/@rolldown/binding-win32-arm64-msvc": { "node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz",
"integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1914,9 +1931,9 @@
} }
}, },
"node_modules/@rolldown/binding-win32-x64-msvc": { "node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz",
"integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2718,9 +2735,9 @@
} }
}, },
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.11.20", "version": "2.11.21",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz",
"integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@ -2744,9 +2761,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.28.8", "version": "4.28.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz",
"integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -2764,11 +2781,11 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.11.12", "baseline-browser-mapping": "^2.11.20",
"caniuse-lite": "^1.0.30001809", "caniuse-lite": "^1.0.30001810",
"electron-to-chromium": "^1.5.402", "electron-to-chromium": "^1.5.420",
"node-releases": "^2.0.53", "node-releases": "^2.0.54",
"update-browserslist-db": "^1.3.0" "update-browserslist-db": "^1.3.2"
}, },
"bin": { "bin": {
"browserslist": "cli.js" "browserslist": "cli.js"
@ -3060,9 +3077,9 @@
} }
}, },
"node_modules/devalue": { "node_modules/devalue": {
"version": "5.9.0", "version": "5.9.2",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.2.tgz",
"integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==", "integrity": "sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -3098,9 +3115,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.417", "version": "1.5.422",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.417.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz",
"integrity": "sha512-4T+DTDWuMPM4aHlHwWdAVCVWwp7LDilnhzkj+c/Lbj91XSQrLuOmZSLtS9Q4iIqjlPUbPOnC624zDVVHCHaolQ==", "integrity": "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -3280,9 +3297,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/esrap": { "node_modules/esrap": {
"version": "2.3.4", "version": "2.3.7",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.4.tgz", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.7.tgz",
"integrity": "sha512-CHnJXZDfBeOAjLIGNy/0BEsmDu5+irHgUXRW9pGptxCbO3uyjJ98crilzFgiBqizW94UodjX3GAPLg/dhEnUfA==", "integrity": "sha512-n2nf7fZR3c9yXf0BPEuHuXqT+KW0SJVj4cN5FMEkpCZ3scLjOQWpiccyCxVzCC2q1wubTghuEGzngJY/7Ah0Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -3342,9 +3359,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-uri": { "node_modules/fast-uri": {
"version": "3.1.6", "version": "3.1.7",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
"integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -4620,9 +4637,9 @@
} }
}, },
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "1.2.0", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.0.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz",
"integrity": "sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==", "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -4837,9 +4854,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.5", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -4860,9 +4877,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.26", "version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
"integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -4880,7 +4897,7 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.17", "nanoid": "^3.3.18",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
@ -5036,13 +5053,13 @@
} }
}, },
"node_modules/rolldown": { "node_modules/rolldown": {
"version": "1.2.4", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz",
"integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@oxc-project/types": "=0.144.0", "@oxc-project/types": "=0.148.0",
"@rolldown/pluginutils": "^1.0.0" "@rolldown/pluginutils": "^1.0.0"
}, },
"bin": { "bin": {
@ -5052,20 +5069,21 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rolldown/binding-android-arm64": "1.2.4", "@rolldown/binding-android-arm-eabi": "1.2.7",
"@rolldown/binding-darwin-arm64": "1.2.4", "@rolldown/binding-android-arm64": "1.2.7",
"@rolldown/binding-darwin-x64": "1.2.4", "@rolldown/binding-darwin-arm64": "1.2.7",
"@rolldown/binding-freebsd-x64": "1.2.4", "@rolldown/binding-darwin-x64": "1.2.7",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.4", "@rolldown/binding-freebsd-x64": "1.2.7",
"@rolldown/binding-linux-arm64-gnu": "1.2.4", "@rolldown/binding-linux-arm-gnueabihf": "1.2.7",
"@rolldown/binding-linux-arm64-musl": "1.2.4", "@rolldown/binding-linux-arm64-gnu": "1.2.7",
"@rolldown/binding-linux-ppc64-gnu": "1.2.4", "@rolldown/binding-linux-arm64-musl": "1.2.7",
"@rolldown/binding-linux-s390x-gnu": "1.2.4", "@rolldown/binding-linux-ppc64-gnu": "1.2.7",
"@rolldown/binding-linux-x64-gnu": "1.2.4", "@rolldown/binding-linux-s390x-gnu": "1.2.7",
"@rolldown/binding-linux-x64-musl": "1.2.4", "@rolldown/binding-linux-x64-gnu": "1.2.7",
"@rolldown/binding-openharmony-arm64": "1.2.4", "@rolldown/binding-linux-x64-musl": "1.2.7",
"@rolldown/binding-win32-arm64-msvc": "1.2.4", "@rolldown/binding-openharmony-arm64": "1.2.7",
"@rolldown/binding-win32-x64-msvc": "1.2.4" "@rolldown/binding-win32-arm64-msvc": "1.2.7",
"@rolldown/binding-win32-x64-msvc": "1.2.7"
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
@ -5542,9 +5560,9 @@
} }
}, },
"node_modules/svelte": { "node_modules/svelte": {
"version": "5.56.9", "version": "5.57.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.9.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.57.0.tgz",
"integrity": "sha512-VT8kSnlEg8069w7AiCcAk3Yf5xvMnrGTagVOmU/OpOLHaHnNqXhWZCH/4EVga/bT/HtWhvE6/fHrXLErx7OnJA==", "integrity": "sha512-NdbDn7fl4be1ViUG0oq/lvG6OZy3oENolV2ONjiqqsfVoeAfzaQAKUcEX3MrQod/Bebv1PgwET9rfXhgn9s4Kg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5552,7 +5570,6 @@
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.10", "@sveltejs/acorn-typescript": "^1.0.10",
"@types/estree": "^1.0.5", "@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1", "acorn": "^8.12.1",
"aria-query": "5.3.1", "aria-query": "5.3.1",
"axobject-query": "^4.1.0", "axobject-query": "^4.1.0",
@ -5864,16 +5881,16 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "8.2.1", "version": "8.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
"integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lightningcss": "^1.33.0", "lightningcss": "^1.33.0",
"picomatch": "^4.0.5", "picomatch": "^4.0.5",
"postcss": "^8.5.25", "postcss": "^8.5.26",
"rolldown": "~1.2.1", "rolldown": "~1.2.4",
"tinyglobby": "^0.2.17" "tinyglobby": "^0.2.17"
}, },
"bin": { "bin": {
@ -5890,7 +5907,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0", "@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.4.0", "@vitejs/devtools": "^0.4.0 || ^0.5.0",
"esbuild": "^0.27.0 || ^0.28.0", "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0", "jiti": ">=1.21.0",
"less": "^4.0.0", "less": "^4.0.0",
@ -6325,9 +6342,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/zimmerframe": { "node_modules/zimmerframe": {
"version": "1.1.4", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.5.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", "integrity": "sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
} }

323
src/lib/PaymentPage.svelte Normal file
View File

@ -0,0 +1,323 @@
<script>
let {
onBack = () => {},
totalPrice = 0,
onConfirmPayment = () => {}
} = $props();
const phoneNumber = '+7 (999) 123-45-67'; // Замените на ваш номер
let copySuccess = $state(false);
async function copyPhoneNumber() {
const cleanNumber = phoneNumber.replace(/[^0-9+]/g, '');
let success = false;
// Основной метод (Clipboard API)
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(cleanNumber);
success = true;
} catch (err) {
console.warn('Clipboard API failed:', err);
}
}
// Fallback для мобильных браузеров и HTTP
if (!success) {
try {
const textArea = document.createElement('textarea');
textArea.value = cleanNumber;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const deprecated = /** @type {any} */ (document).execCommand;
if (deprecated && deprecated.call(document, 'copy')) {
success = true;
}
document.body.removeChild(textArea);
} catch (err) {
console.error('Fallback copy failed:', err);
}
}
if (success) {
copySuccess = true;
setTimeout(() => {
copySuccess = false;
}, 2000);
}
}
</script>
<div class="payment-container">
<header class="payment-header">
<button class="back-btn" onclick={onBack}> Назад</button>
<h1>Оплата печати</h1>
</header>
<main class="payment-content">
<!-- Сумма -->
<div class="amount-block">
<span class="amount-label">К оплате</span>
<span class="amount-value">{totalPrice}</span>
</div>
<!-- Информация о переводе -->
<div class="transfer-info">
<!-- Сначала номер телефона -->
<div class="phone-row">
<span class="phone-number">{phoneNumber}</span>
<button
class="copy-btn"
onclick={copyPhoneNumber}
class:success={copySuccess}
title={copySuccess ? 'Скопировано' : 'Копировать номер'}
aria-label={copySuccess ? 'Скопировано' : 'Копировать номер телефона'}
>
{copySuccess ? '✓' : '📋'}
</button>
</div>
<!-- Под номером — информация о банке -->
<p class="info-text">
Переводить на <span class="bank-name">Т-Банк</span>
</p>
</div>
<!-- Шаги оплаты -->
<div class="steps-block">
<h3 class="steps-title">Как оплатить:</h3>
<ol class="steps-list">
<li>Скопируйте номер телефона</li>
<li>Откройте банковское приложение</li>
<li>Выберите перевод по номеру телефона</li>
<li>Вставьте номер <strong>{phoneNumber}</strong></li>
<li>Укажите банк: <span class="bank-name">Т-Банк</span></li>
<li>Введите сумму <strong>{totalPrice}</strong></li>
<li>Подтвердите перевод</li>
<li>Вернитесь сюда и нажмите кнопку ниже</li>
</ol>
</div>
<!-- Кнопка подтверждения -->
<div class="payment-actions">
<button
type="button"
class="confirm-btn"
onclick={onConfirmPayment}
>
Я оплатил(а) — Печать
</button>
</div>
</main>
</div>
<style>
.payment-container {
width: 100%;
min-height: 100dvh;
display: flex;
flex-direction: column;
background: radial-gradient(120% 60% at 50% -10%, rgba(99, 102, 241, 0.18) 0%, transparent 60%);
font-family: system-ui, -apple-system, sans-serif;
color: #f5f7fa;
position: fixed;
top: 0;
left: 0;
z-index: 2000;
/* Предотвращает горизонтальный скролл на мобильных */
overflow-x: hidden;
}
.payment-header {
padding: 20px 16px 8px;
display: flex; /* Делаем контейнер флексом */
align-items: center; /* Выравниваем по вертикали */
position: relative; /* Для позиционирования, если понадобится */
}
.back-btn {
background: none;
border: none;
color: #6366f1;
font-size: 16px;
font-weight: 600;
cursor: pointer;
padding: 12px 8px;
margin: -8px -8px -8px 0; /* Убираем лишние отступы, оставляем нулевой слева */
min-height: 44px;
display: inline-flex;
align-items: center;
order: -1; /* Принудительно ставим кнопку первой в потоке */
margin-right: auto; /* Теперь это сработает и прижмет кнопку к левому краю */
}
.payment-header h1 {
margin: 0;
font-size: 26px;
font-weight: 700;
letter-spacing: -0.02em;
flex-grow: 1; /* Заставляем заголовок занимать оставшееся место */
text-align: center; /* Центрируем текст заголовка */
}
.payment-content {
flex: 1;
padding: 24px 16px 32px;
display: flex;
flex-direction: column;
gap: 28px;
max-width: 520px;
margin: 0 auto;
width: 100%;
box-sizing: border-box;
}
/* ── Сумма ── */
.amount-block {
text-align: center;
display: flex;
flex-direction: column;
gap: 4px;
}
.amount-label {
font-size: 13px;
color: #8b93a1;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.amount-value {
font-size: 48px;
font-weight: 800;
color: #fff;
letter-spacing: -0.03em;
line-height: 1;
}
/* ── Информация о переводе ── */
.transfer-info {
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;
}
.phone-row {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: rgba(255, 255, 255, 0.05);
border-radius: 14px;
width: 100%;
box-sizing: border-box;
justify-content: center;
}
.phone-number {
font-size: 20px;
font-weight: 600;
color: #fff;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
}
.copy-btn {
/* Минимум 44px для touch target на мобильных */
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(99, 102, 241, 0.2);
border: 1px solid rgba(99, 102, 241, 0.4);
border-radius: 12px;
color: #fff;
font-size: 22px;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
padding: 0;
/* Убирает задержку тапа на мобильных */
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.copy-btn:hover {
background: rgba(99, 102, 241, 0.35);
border-color: #6366f1;
}
.copy-btn:active {
transform: scale(0.95);
}
.copy-btn.success {
background: rgba(34, 197, 94, 0.25);
border-color: rgba(34, 197, 94, 0.5);
}
.info-text {
color: #a9b0c0;
font-size: 15px;
line-height: 1.5;
margin: 0;
text-align: center;
}
.bank-name {
color: #fbbf24;
font-weight: 700;
}
/* ── Шаги ── */
.steps-block {
display: flex;
flex-direction: column;
gap: 10px;
}
.steps-title {
margin: 0;
font-size: 17px;
font-weight: 600;
color: #f5f7fa;
text-align: left;
}
.steps-list {
margin: 0;
padding-left: 20px;
color: #a9b0c0;
font-size: 15px;
line-height: 1.9;
text-align: left;
}
.steps-list li {
padding-left: 4px;
}
.steps-list strong {
color: #fff;
font-weight: 600;
}
/* ── Кнопка подтверждения ── */
.payment-actions {
display: flex;
flex-direction: column;
gap: 14px;
margin-top: auto;
padding-top: 8px;
}
.confirm-btn {
width: 100%;
/* Увеличенная высота для мобильных */
padding: 20px 24px;
border-radius: 16px;
font-size: 17px;
font-weight: 700;
cursor: pointer;
border: none;
color: #fff;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
box-shadow: 0 8px 24px -6px rgba(34, 197, 94, 0.4);
transition: all 0.2s ease;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
min-height: 56px;
}
.confirm-btn:hover {
transform: translateY(-2px);
box-shadow: 0 12px 28px -6px rgba(34, 197, 94, 0.5);
}
.confirm-btn:active {
transform: translateY(0);
}
/* Адаптация для очень маленьких экранов */
@media (max-width: 360px) {
.amount-value {
font-size: 40px;
}
.phone-number {
font-size: 18px;
}
.payment-header h1 {
font-size: 22px;
}
}
</style>

View File

@ -2,6 +2,7 @@
import { onDestroy, tick } from 'svelte'; import { onDestroy, tick } from 'svelte';
import SelectionPage from './preview/SelectionPage.svelte'; import SelectionPage from './preview/SelectionPage.svelte';
import PreviewPage from './preview/PreviewPage.svelte'; import PreviewPage from './preview/PreviewPage.svelte';
import PaymentPage from './PaymentPage.svelte';
let { onBack = () => {} } = $props(); let { onBack = () => {} } = $props();
@ -16,6 +17,9 @@
/** Режим просмотра: 'gallery' (выбор страниц) или 'preview' (чтение) */ /** Режим просмотра: 'gallery' (выбор страниц) или 'preview' (чтение) */
let viewMode = $state(/** @type {'gallery' | 'preview'} */ ('gallery')); let viewMode = $state(/** @type {'gallery' | 'preview'} */ ('gallery'));
// ── Состояние оплаты ──
let showPayment = $state(false);
// ── Данные для галереи ( выбор страниц) ── // ── Данные для галереи ( выбор страниц) ──
let galleryThumbnails = $state([]); let galleryThumbnails = $state([]);
let galleryLoading = $state(false); let galleryLoading = $state(false);
@ -327,8 +331,14 @@
else openPreview(file); else openPreview(file);
} }
// ── НОВАЯ ФУНКЦИЯ: Отправка на сервер ── // ── Показ страницы оплаты ──
async function submitPrint() { function showPaymentPage() {
if (files.length === 0 || isPrinting) return;
showPayment = true;
}
// ── Реальная отправка на сервер (после оплаты) ──
async function realSubmitPrint() {
if (files.length === 0 || isPrinting) return; if (files.length === 0 || isPrinting) return;
isPrinting = true; isPrinting = true;
@ -365,17 +375,32 @@
alert(`⚠️ Не удалось связаться с сервером печати.`); alert(`⚠️ Не удалось связаться с сервером печати.`);
} finally { } finally {
isPrinting = false; isPrinting = false;
showPayment = false;
} }
} }
</script> </script>
{#if !activeFile} <!-- ═══════════════════════════════════════════════════════════ -->
<!-- СТРАНИЦА ОПЛАТЫ (полный экран) -->
<!-- ═══════════════════════════════════════════════════════════ -->
{#if showPayment}
<PaymentPage
totalPrice={totalPrice()}
filesCount={files.length}
onBack={() => showPayment = false}
onConfirmPayment={realSubmitPrint}
/>
{/if}
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- ОСНОВНАЯ СТРАНИЦА ПЕЧАТИ -->
<!-- ═══════════════════════════════════════════════════════════ -->
{#if !activeFile && !showPayment}
<div class="page-container"> <div class="page-container">
<header class="header"> <header class="header">
<button class="back-btn" onclick={onBack}> Назад</button> <button class="back-btn" onclick={onBack}> Назад</button>
<h1>Печать</h1> <h1>Печать</h1>
</header> </header>
<main class="options"> <main class="options">
{#if files.length === 0} {#if files.length === 0}
<div class="empty-upload"> <div class="empty-upload">
@ -395,7 +420,6 @@
<button type="button" class="icon-btn danger" onclick={() => removeFile(file)} title="Удалить"></button> <button type="button" class="icon-btn danger" onclick={() => removeFile(file)} title="Удалить"></button>
</span> </span>
</div> </div>
{#if file.expanded} {#if file.expanded}
<div class="file-options"> <div class="file-options">
<div class="option-sub print-params-row"> <div class="option-sub print-params-row">
@ -414,7 +438,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="option-sub"> <div class="option-sub">
<label class="option-title">Формат</label> <label class="option-title">Формат</label>
<div class="toggle-group horizontal"> <div class="toggle-group horizontal">
@ -423,7 +446,6 @@
{/each} {/each}
</div> </div>
</div> </div>
<div class="option-sub"> <div class="option-sub">
<label class="option-title">Цветность</label> <label class="option-title">Цветность</label>
<div class="toggle-group horizontal"> <div class="toggle-group horizontal">
@ -432,7 +454,6 @@
{/each} {/each}
</div> </div>
</div> </div>
<div class="option-sub quality-sub"> <div class="option-sub quality-sub">
<label class="option-title">Качество {qualities[file.qualityIndex].label}</label> <label class="option-title">Качество {qualities[file.qualityIndex].label}</label>
<input <input
@ -456,7 +477,6 @@
<button type="button" class="add-file-btn" onclick={triggerFileInput}> Добавить файлы</button> <button type="button" class="add-file-btn" onclick={triggerFileInput}> Добавить файлы</button>
{/if} {/if}
</main> </main>
<footer class="actions"> <footer class="actions">
{#if files.length > 0} {#if files.length > 0}
<p class="file-count"> <p class="file-count">
@ -468,14 +488,14 @@
<span class="summary-itogo">Итого</span> <span class="summary-itogo">Итого</span>
<span class="summary-price">{totalPrice()}</span> <span class="summary-price">{totalPrice()}</span>
</div> </div>
<!-- ИЗМЕНЕННАЯ КНОПКА --> <!-- КНОПКА ОТКРЫВАЕТ СТРАНИЦУ ОПЛАТЫ -->
<button <button
type="button" type="button"
class="action-btn secondary" class="action-btn secondary"
onclick={submitPrint} onclick={showPaymentPage}
disabled={isPrinting || files.length === 0} disabled={isPrinting || files.length === 0}
> >
{isPrinting ? 'Отправка...' : 'Печать'} {isPrinting ? 'Отправка...' : 'Далее'}
</button> </button>
<input type="file" bind:this={fileInput} accept="image/*,.pdf" multiple onchange={handleFileSelect} style="display:none" /> <input type="file" bind:this={fileInput} accept="image/*,.pdf" multiple onchange={handleFileSelect} style="display:none" />
</footer> </footer>
@ -483,7 +503,7 @@
{/if} {/if}
<!-- ═══════════════════════════════════════════════════════════ --> <!-- ═══════════════════════════════════════════════════════════ -->
<!-- ДЕЛЕГИРОВАНИЕ В ДОЧЕРНИЕ КОМПОНЕНТЫ --> <!-- ОВЕРЛЕЙ ПРОСМОТРА/ГАЛЕРЕИ -->
<!-- ═══════════════════════════════════════════════════════════ --> <!-- ═══════════════════════════════════════════════════════════ -->
{#if activeFile} {#if activeFile}
<div class="overlay-backdrop" onclick={closeOverlay} role="dialog" aria-modal="true"> <div class="overlay-backdrop" onclick={closeOverlay} role="dialog" aria-modal="true">