Создан компонент поста
This commit is contained in:
parent
674ef7fe04
commit
d0f41672d1
32 changed files with 2082 additions and 289 deletions
101
bun.lock
101
bun.lock
|
|
@ -9,6 +9,7 @@
|
|||
"name": "frontend",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^5.0.3",
|
||||
"@astrojs/node": "^10.0.4",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"astro": "^6.0.8",
|
||||
|
|
@ -31,6 +32,8 @@
|
|||
|
||||
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.1.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-P+HnCsu2js3BoTc8kFmu+E9gOcFeMdPris75g+Zl4sY8+bBRbSQV6xzcBDbZ27eE7yBGEGQoqjpChx+KJYIPYQ=="],
|
||||
|
||||
"@astrojs/mdx": ["@astrojs/mdx@5.0.3", "", { "dependencies": { "@astrojs/markdown-remark": "7.1.0", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^6.0.0" } }, "sha512-zv/OlM5sZZvyjHqJjR3FjJvoCgbxdqj3t4jO/gSEUNcck3BjdtMgNQw8UgPfAGe4yySdG4vjZ3OC5wUxhu7ckg=="],
|
||||
|
||||
"@astrojs/node": ["@astrojs/node@10.0.4", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^6.0.0" } }, "sha512-7pVgiVSscQHRC2WqjlXcnbbcKMYp2GXrYpmuvdGg5zgA8J1lFm2vmwVhHZFuZK3Ik5PzoxiDROaEgoDGLbfhLw=="],
|
||||
|
||||
"@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="],
|
||||
|
|
@ -181,6 +184,8 @@
|
|||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
|
||||
|
||||
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
|
@ -285,10 +290,14 @@
|
|||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
|
||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||
|
||||
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
|
||||
|
|
@ -313,6 +322,10 @@
|
|||
|
||||
"@vscode/l10n": ["@vscode/l10n@0.0.18", "", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
|
||||
|
|
@ -329,6 +342,8 @@
|
|||
|
||||
"array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="],
|
||||
|
||||
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
|
||||
|
||||
"astro": ["astro@6.1.4", "", { "dependencies": { "@astrojs/compiler": "^3.0.1", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.1.0", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-SRy1bONuCHkGWhI5JiWCQKVDVbeaXOikjAVZs/Nz+lvUvubtdLoZfnacmuZHQ9RL2IOkU54M8/qZYm9ypJDKrg=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
|
@ -345,6 +360,8 @@
|
|||
|
||||
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||
|
||||
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
|
||||
|
|
@ -353,6 +370,8 @@
|
|||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
|
@ -423,6 +442,10 @@
|
|||
|
||||
"es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
|
||||
|
||||
"esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
|
||||
|
||||
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
|
@ -431,7 +454,19 @@
|
|||
|
||||
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
"estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="],
|
||||
|
||||
"estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="],
|
||||
|
||||
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
|
||||
|
||||
"estree-util-scope": ["estree-util-scope@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0" } }, "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ=="],
|
||||
|
||||
"estree-util-to-js": ["estree-util-to-js@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "astring": "^1.8.0", "source-map": "^0.7.0" } }, "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg=="],
|
||||
|
||||
"estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="],
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
|
|
@ -481,8 +516,12 @@
|
|||
|
||||
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
|
||||
|
||||
"hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="],
|
||||
|
||||
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||
|
||||
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
||||
|
||||
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="],
|
||||
|
||||
"hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="],
|
||||
|
|
@ -501,12 +540,22 @@
|
|||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||
|
||||
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
|
||||
|
||||
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
|
||||
|
||||
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
|
||||
|
||||
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
||||
|
||||
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
|
||||
|
||||
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
|
@ -555,6 +604,8 @@
|
|||
|
||||
"magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="],
|
||||
|
||||
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||
|
||||
"mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
|
||||
|
|
@ -575,6 +626,14 @@
|
|||
|
||||
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
|
||||
|
||||
"mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="],
|
||||
|
||||
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
|
||||
|
||||
"mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
|
||||
|
||||
"mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
|
||||
|
||||
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
|
||||
|
||||
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
|
||||
|
|
@ -603,10 +662,22 @@
|
|||
|
||||
"micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
|
||||
|
||||
"micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.1", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q=="],
|
||||
|
||||
"micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="],
|
||||
|
||||
"micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="],
|
||||
|
||||
"micromark-extension-mdxjs": ["micromark-extension-mdxjs@3.0.0", "", { "dependencies": { "acorn": "^8.0.0", "acorn-jsx": "^5.0.0", "micromark-extension-mdx-expression": "^3.0.0", "micromark-extension-mdx-jsx": "^3.0.0", "micromark-extension-mdx-md": "^2.0.0", "micromark-extension-mdxjs-esm": "^3.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ=="],
|
||||
|
||||
"micromark-extension-mdxjs-esm": ["micromark-extension-mdxjs-esm@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A=="],
|
||||
|
||||
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
|
||||
|
||||
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
|
||||
|
||||
"micromark-factory-mdx-expression": ["micromark-factory-mdx-expression@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ=="],
|
||||
|
||||
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
|
||||
|
||||
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
|
||||
|
|
@ -627,6 +698,8 @@
|
|||
|
||||
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||
|
||||
"micromark-util-events-to-acorn": ["micromark-util-events-to-acorn@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg=="],
|
||||
|
||||
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
|
||||
|
||||
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
|
||||
|
|
@ -685,6 +758,8 @@
|
|||
|
||||
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="],
|
||||
|
||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
|
@ -711,6 +786,14 @@
|
|||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="],
|
||||
|
||||
"recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="],
|
||||
|
||||
"recma-parse": ["recma-parse@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "esast-util-from-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ=="],
|
||||
|
||||
"recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="],
|
||||
|
||||
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
|
||||
|
||||
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
|
||||
|
|
@ -723,10 +806,14 @@
|
|||
|
||||
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
|
||||
|
||||
"rehype-recma": ["rehype-recma@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "hast-util-to-estree": "^3.0.0" } }, "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw=="],
|
||||
|
||||
"rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="],
|
||||
|
||||
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
||||
|
||||
"remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="],
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
|
@ -769,6 +856,8 @@
|
|||
|
||||
"smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="],
|
||||
|
||||
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
|
@ -781,6 +870,10 @@
|
|||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||
|
||||
"svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
|
||||
|
|
@ -829,6 +922,8 @@
|
|||
|
||||
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||
|
||||
"unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="],
|
||||
|
||||
"unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="],
|
||||
|
||||
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||
|
|
@ -911,6 +1006,8 @@
|
|||
|
||||
"@astrojs/language-server/@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
|
|
@ -929,6 +1026,8 @@
|
|||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
|
||||
"unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||
|
||||
"vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import node from '@astrojs/node';
|
||||
import mdx from '@astrojs/mdx';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [mdx()],
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^5.0.3",
|
||||
"@astrojs/node": "^10.0.4",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"astro": "^6.0.8",
|
||||
|
|
|
|||
BIN
frontend/public/images/contacts/conBg.avif
Normal file
BIN
frontend/public/images/contacts/conBg.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
frontend/public/images/contacts/conImg.avif
Normal file
BIN
frontend/public/images/contacts/conImg.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/public/images/reviews/revBg.avif
Normal file
BIN
frontend/public/images/reviews/revBg.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
frontend/public/images/reviews/revImg.avif
Normal file
BIN
frontend/public/images/reviews/revImg.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
73
frontend/src/components/base/AuthLockBlock.astro
Normal file
73
frontend/src/components/base/AuthLockBlock.astro
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
buttonText: string;
|
||||
buttonHref: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title = 'Авторизуйтесь, чтобы продолжить',
|
||||
description = 'Для продолжения, пожалуйста, войдите в личный кабинет.',
|
||||
buttonText = 'Войти в кабинет',
|
||||
buttonHref = '/auth/sign-in',
|
||||
className = ''
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class={`auth-lock-card ${className}`}>
|
||||
<div class="lock-icon-container">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lock-icon">
|
||||
<rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="lock-title">{title}</h3>
|
||||
<p class="lock-text" style:display={description ? 'block' : 'none'}>{description}</p>
|
||||
<a href={buttonHref} class="auth-button">{buttonText}</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.auth-lock-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #f8fafc;
|
||||
border: 2px dashed #e2e8f0;
|
||||
border-radius: 1.5rem;
|
||||
padding: 4rem 2rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lock-icon-container {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
background: #ffffff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.lock-icon { width: 2rem; height: 2rem; }
|
||||
.lock-title { color: #1e293b; font-size: 1.5rem; font-weight: 700; margin: 0 0 1rem; }
|
||||
.lock-text { color: #64748b; line-height: 1.6; margin: 0 0 2rem; max-width: 400px; }
|
||||
|
||||
.auth-button {
|
||||
background: #1e293b;
|
||||
color: #ffffff;
|
||||
padding: 0.875rem 2rem;
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auth-button:hover { background: #0f172a; transform: translateY(-2px); }
|
||||
</style>
|
||||
|
|
@ -1,17 +1,25 @@
|
|||
---
|
||||
import { blogPosts } from '@data/blogData';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
|
||||
// Сортируем и форматируем для поиска
|
||||
const searchData = posts
|
||||
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
||||
.map(post => ({
|
||||
title: post.data.title,
|
||||
description: post.data.description,
|
||||
slug: post.id,
|
||||
category: post.data.category,
|
||||
categoryColor: post.data.categoryColor,
|
||||
date: post.data.date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})
|
||||
}));
|
||||
|
||||
const title = 'Поиск по статьям';
|
||||
|
||||
const searchData = blogPosts.map(post => ({
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
slug: post.slug,
|
||||
category: post.category,
|
||||
categoryColor: post.categoryColor,
|
||||
date: post.date
|
||||
}));
|
||||
|
||||
const postsJson = JSON.stringify(searchData);
|
||||
---
|
||||
|
||||
|
|
@ -106,7 +114,7 @@ const postsJson = JSON.stringify(searchData);
|
|||
|
||||
// вывод динамического списка статей
|
||||
resultsContainer.innerHTML = filtered.map(post => `
|
||||
<a href="/blog/${post.slug}" class="search-result-item">
|
||||
<a href="/blog/${post.id}" class="search-result-item">
|
||||
<span class="result-date">${post.date}</span>
|
||||
<h4 class="result-title">${post.title}</h4>
|
||||
<p class="result-description">${post.description.substring(0, 120)}...</p>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const formatDate = (dateStr: string) => {
|
|||
};
|
||||
---
|
||||
|
||||
<article class="blog-card animate-on-scroll" data-animation="fade-up">
|
||||
<article class="blog-card" data-animation="fade-up">
|
||||
<a href={slug} class="card-link">
|
||||
<!-- Изображение -->
|
||||
<div class="card-image">
|
||||
|
|
|
|||
346
frontend/src/components/blog/PostCommentForm.astro
Normal file
346
frontend/src/components/blog/PostCommentForm.astro
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
---
|
||||
import AuthLockBlock from '@components/base/AuthLockBlock.astro';
|
||||
|
||||
interface Props {
|
||||
postId: string;
|
||||
isAuthorized?: boolean;
|
||||
}
|
||||
|
||||
const { postId, isAuthorized = false } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="comment-section" data-post-id={postId}>
|
||||
<h3 class="comment-title">Комментарии</h3>
|
||||
|
||||
{isAuthorized ? (
|
||||
<form class="comment-form" id="comment-form" data-post-id={postId}>
|
||||
<div class="form-group">
|
||||
<label for="comment-text" class="form-label">Ваш комментарий *</label>
|
||||
<textarea
|
||||
id="comment-text"
|
||||
name="comment"
|
||||
class="form-textarea"
|
||||
placeholder="Напишите ваш комментарий..."
|
||||
rows="4"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="submit-btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="btn-icon">
|
||||
<path d="m22 2-7 20-4-9-9-4Z"></path>
|
||||
<path d="M22 2 11 13"></path>
|
||||
</svg>
|
||||
Отправить комментарий
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<AuthLockBlock
|
||||
title="Авторизуйтесь, чтобы оставить комментарий"
|
||||
description=""
|
||||
buttonText="Войти в кабинет"
|
||||
buttonHref="/auth/sign-in"
|
||||
/>
|
||||
)}
|
||||
|
||||
<!-- Список комментариев -->
|
||||
<div class="comments-list" id="comments-list">
|
||||
<!-- Пример комментарителя (заглушка для демонстрации) -->
|
||||
<div class="comment-item">
|
||||
<div class="comment-header">
|
||||
<div class="comment-author">
|
||||
<div class="author-avatar">А</div>
|
||||
<div class="author-info">
|
||||
<span class="author-name">Алексей Петров</span>
|
||||
<span class="comment-date">2 апреля 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<p>Спасибо за полезную статью! Столкнулся с похожей ситуацией, теперь знаю куда обращаться за помощью.</p>
|
||||
</div>
|
||||
<button class="comment-reply-btn">Ответить</button>
|
||||
|
||||
<!-- Ответ на комментарий -->
|
||||
<div class="comment-reply">
|
||||
<div class="comment-header">
|
||||
<div class="comment-author">
|
||||
<div class="author-avatar reply-avatar">Ю</div>
|
||||
<div class="author-info">
|
||||
<span class="author-name">Юрист АВ</span>
|
||||
<span class="comment-date">3 апреля 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<p>Алексей, спасибо за ваш комментарий! Обращайтесь — мы всегда готовы помочь в решении автоспоров.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ещё один комментарий -->
|
||||
<div class="comment-item">
|
||||
<div class="comment-header">
|
||||
<div class="comment-author">
|
||||
<div class="author-avatar" style="background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);">М</div>
|
||||
<div class="author-info">
|
||||
<span class="author-name">Мария Иванова</span>
|
||||
<span class="comment-date">5 апреля 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<p>Очень подробная инструкция! Подскажите, а если ДТП произошло без пострадавших, можно ли обойтись без вызова ГИБДД?</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.comment-section {
|
||||
margin-top: 3rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 2px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.comment-title {
|
||||
color: #1e293b;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
|
||||
.comment-form {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
padding: 1rem;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
outline: none;
|
||||
background: #f8fafc;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.form-textarea:focus {
|
||||
border-color: #d4af37;
|
||||
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.1);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: linear-gradient(135deg, #d4af37 0%, #eac26e 100%);
|
||||
color: #1e293b;
|
||||
border: none;
|
||||
padding: 0.875rem 1.75rem;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 15px rgba(212, 175, 55, 0.3);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(212, 175, 55, 0.4);
|
||||
}
|
||||
|
||||
/* Auth Lock Card - на всю ширину */
|
||||
.comment-section :global(.auth-lock-card) {
|
||||
max-width: 100% !important;
|
||||
padding: 2rem 1.5rem !important;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.comment-section :global(.lock-icon-container) {
|
||||
width: 3rem !important;
|
||||
height: 3rem !important;
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
.comment-section :global(.lock-icon) {
|
||||
width: 1.25rem !important;
|
||||
height: 1.25rem !important;
|
||||
}
|
||||
|
||||
.comment-section :global(.lock-title) {
|
||||
font-size: 1.1rem !important;
|
||||
margin-bottom: 0.75rem !important;
|
||||
}
|
||||
|
||||
.comment-section :global(.lock-text) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.comment-section :global(.auth-button) {
|
||||
padding: 0.6rem 1.5rem !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
/* Comments List */
|
||||
.comments-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.comment-item {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.author-avatar {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #d4af37 0%, #eac26e 100%);
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.reply-avatar {
|
||||
background: linear-gradient(135deg, #22c55e 0%, #4ade80 100%) !important;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
color: #1e293b;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.comment-date {
|
||||
color: #94a3b8;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
color: #475569;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.comment-content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.comment-reply-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #d4af37;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 0;
|
||||
margin-top: 0.5rem;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.comment-reply-btn:hover {
|
||||
color: #b8942e;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Ответ на комментарий */
|
||||
.comment-reply {
|
||||
margin-top: 1rem;
|
||||
margin-left: 2rem;
|
||||
padding: 1rem 1.25rem;
|
||||
background: #f8fafc;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-left: 3px solid #d4af37;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.comment-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.comment-reply {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const setupCommentForm = () => {
|
||||
const form = document.getElementById('comment-form') as HTMLFormElement;
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
const comment = formData.get('comment');
|
||||
|
||||
if (comment) {
|
||||
console.log('Новый комментарий:', comment);
|
||||
alert('Спасибо! Ваш комментарий добавлен.');
|
||||
form.reset();
|
||||
|
||||
// Здесь можно добавить логику для добавления комментарителя в список
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setupCommentForm();
|
||||
document.addEventListener('astro:page-load', setupCommentForm);
|
||||
</script>
|
||||
139
frontend/src/components/blog/PostReactionButtons.astro
Normal file
139
frontend/src/components/blog/PostReactionButtons.astro
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
interface Props {
|
||||
initialLikes?: number;
|
||||
initialDislikes?: number;
|
||||
postId: string;
|
||||
}
|
||||
|
||||
const { initialLikes = 0, initialDislikes = 0, postId } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="post-reactions" data-post-id={postId}>
|
||||
<button class="reaction-btn like-btn" data-action="like" aria-label="Нравится">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="reaction-icon">
|
||||
<path d="M7 10v12"></path>
|
||||
<path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"></path>
|
||||
</svg>
|
||||
<span class="reaction-count" data-count="likes">{initialLikes}</span>
|
||||
</button>
|
||||
|
||||
<button class="reaction-btn dislike-btn" data-action="dislike" aria-label="Не нравится">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="reaction-icon">
|
||||
<path d="M17 14V2"></path>
|
||||
<path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"></path>
|
||||
</svg>
|
||||
<span class="reaction-count" data-count="dislikes">{initialDislikes}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-reactions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.reaction-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 0.875rem;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.reaction-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.reaction-count {
|
||||
min-width: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.like-btn:hover,
|
||||
.like-btn.active {
|
||||
background: #22c55e;
|
||||
border-color: #22c55e;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.dislike-btn:hover,
|
||||
.dislike-btn.active {
|
||||
background: #ef4444;
|
||||
border-color: #ef4444;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.reaction-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.reaction-btn:hover .reaction-icon {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.reaction-btn {
|
||||
padding: 0.4rem 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.post-reactions').forEach((container) => {
|
||||
const likeBtn = container.querySelector('.like-btn');
|
||||
const dislikeBtn = container.querySelector('.dislike-btn');
|
||||
const likesCount = container.querySelector('[data-count="likes"]');
|
||||
const dislikesCount = container.querySelector('[data-count="dislikes"]');
|
||||
|
||||
let likes = parseInt(likesCount?.textContent || '0');
|
||||
let dislikes = parseInt(dislikesCount?.textContent || '0');
|
||||
let userAction: 'like' | 'dislike' | null = null;
|
||||
|
||||
likeBtn?.addEventListener('click', () => {
|
||||
if (userAction === 'like') {
|
||||
likes--;
|
||||
userAction = null;
|
||||
likeBtn.classList.remove('active');
|
||||
} else {
|
||||
if (userAction === 'dislike') {
|
||||
dislikes--;
|
||||
dislikeBtn.classList.remove('active');
|
||||
}
|
||||
likes++;
|
||||
userAction = 'like';
|
||||
likeBtn.classList.add('active');
|
||||
}
|
||||
if (likesCount) likesCount.textContent = likes.toString();
|
||||
if (dislikesCount) dislikesCount.textContent = dislikes.toString();
|
||||
});
|
||||
|
||||
dislikeBtn?.addEventListener('click', () => {
|
||||
if (userAction === 'dislike') {
|
||||
dislikes--;
|
||||
userAction = null;
|
||||
dislikeBtn.classList.remove('active');
|
||||
} else {
|
||||
if (userAction === 'like') {
|
||||
likes--;
|
||||
likeBtn.classList.remove('active');
|
||||
}
|
||||
dislikes++;
|
||||
userAction = 'dislike';
|
||||
dislikeBtn.classList.add('active');
|
||||
}
|
||||
if (likesCount) likesCount.textContent = likes.toString();
|
||||
if (dislikesCount) dislikesCount.textContent = dislikes.toString();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
120
frontend/src/components/blog/PostSocialShare.astro
Normal file
120
frontend/src/components/blog/PostSocialShare.astro
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
url: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const { title, url, className = '' } = Astro.props;
|
||||
|
||||
const encodedTitle = encodeURIComponent(title);
|
||||
const encodedUrl = encodeURIComponent(url);
|
||||
---
|
||||
|
||||
<div class={`post-social-share ${className}`}>
|
||||
<span class="share-label">Поделиться:</span>
|
||||
<div class="social-icons">
|
||||
<!-- Telegram -->
|
||||
<a
|
||||
href={`https://t.me/share/url?url=${encodedUrl}&text=${encodedTitle}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="social-icon telegram"
|
||||
aria-label="Поделиться в Telegram"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- VK -->
|
||||
<a
|
||||
href={`https://vk.com/share.php?url=${encodedUrl}&title=${encodedTitle}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="social-icon vk"
|
||||
aria-label="Поделиться в VK"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M15.684 0H8.316C1.592 0 0 1.592 0 8.316v7.368C0 22.408 1.592 24 8.316 24h7.368C22.408 24 24 22.408 24 15.684V8.316C24 1.592 22.391 0 15.684 0zm3.692 17.123h-1.744c-.66 0-.864-.525-2.05-1.727-1.033-1-1.49-1.135-1.744-1.135-.356 0-.458.102-.458.593v1.575c0 .424-.135.678-1.253.678-1.846 0-3.896-1.118-5.335-3.202C4.624 10.857 4.03 8.57 4.03 8.096c0-.254.102-.491.593-.491h1.744c.44 0 .61.203.78.677.847 2.49 2.27 4.675 2.862 4.675.22 0 .322-.102.322-.66V9.721c-.068-1.186-.695-1.287-.695-1.71 0-.204.17-.407.44-.407h2.744c.373 0 .508.203.508.643v3.473c0 .372.17.508.271.508.22 0 .407-.136.813-.542 1.27-1.422 2.18-3.61 2.18-3.61.119-.254.322-.491.763-.491h1.744c.525 0 .644.27.525.643-.22 1.017-2.354 4.031-2.354 4.031-.186.305-.254.44 0 .78.186.254.796.779 1.203 1.253.745.847 1.32 1.558 1.473 2.05.17.49-.085.744-.576.744z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- WhatsApp -->
|
||||
<a
|
||||
href={`https://api.whatsapp.com/send?text=${encodedTitle}%20${encodedUrl}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="social-icon whatsapp"
|
||||
aria-label="Поделиться в WhatsApp"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- Одноклассники -->
|
||||
<a
|
||||
href={`https://connect.ok.ru/offer?url=${encodedUrl}&title=${encodedTitle}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="social-icon odnoklassniki"
|
||||
aria-label="Поделиться в Одноклассниках"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M14.505 17.043c.098.07.222.102.373.102.15 0 .274-.032.372-.102a.597.597 0 0 0 .214-.348.89.89 0 0 0 .064-.408 1.09 1.09 0 0 0-.136-.443c-.09-.153-.212-.277-.365-.373l-.01-.006c-.154-.097-.275-.222-.363-.373a.997.997 0 0 1-.136-.438.86.86 0 0 1 .063-.397.577.577 0 0 1 .204-.338.636.636 0 0 1 .372-.102c.15 0 .274.034.372.102a.577.577 0 0 1 .204.338.86.86 0 0 1 .063.397.997.997 0 0 1-.136.438c-.088.151-.209.276-.363.373l-.01.006c-.153.096-.275.22-.365.373a1.09 1.09 0 0 0-.136.443.89.89 0 0 0 .064.408.597.597 0 0 0 .214.348zM12 13.5c-.828 0-1.5-.672-1.5-1.5s.672-1.5 1.5-1.5 1.5.672 1.5 1.5-.672 1.5-1.5 1.5zm6.793-9.216A11.952 11.952 0 0 0 12 .5C5.373.5.008 5.865.008 12.5c0 2.768.929 5.372 2.534 7.458.065.085.136.165.208.244.016.018.034.034.05.051l.01.011c.135.147.277.288.425.421.043.039.087.077.131.114.129.111.263.216.4.315.076.056.154.109.233.161.116.077.235.149.357.217.095.054.191.105.29.151.115.054.234.102.355.145.097.034.195.065.295.091.12.032.241.058.365.079.097.016.195.03.293.039.131.012.264.017.398.017.135 0 .268-.005.4-.017.098-.009.196-.023.293-.039.124-.021.245-.047.365-.079.1-.026.198-.057.295-.091.121-.043.24-.091.355-.145.099-.046.195-.097.29-.151.122-.068.241-.14.357-.217.079-.052.157-.105.233-.161.137-.099.271-.204.4-.315.044-.037.088-.075.131-.114.148-.133.29-.274.425-.421l.01-.011c.016-.017.034-.033.05-.051.072-.079.143-.159.208-.244A11.953 11.953 0 0 0 23.992 12.5C23.992 5.865 18.627.5 12 .5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-social-share {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.share-label {
|
||||
color: #64748b;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.social-icons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.social-icon svg {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.social-icon.telegram { background: #0088cc; }
|
||||
.social-icon.vk { background: #0077FF; }
|
||||
.social-icon.whatsapp { background: #25D366; }
|
||||
.social-icon.odnoklassniki { background: #EE8208; }
|
||||
|
||||
.social-icon:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.post-social-share {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
frontend/src/components/blog/RelatedPosts.astro
Normal file
91
frontend/src/components/blog/RelatedPosts.astro
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
import BlogCard from '@components/blog/BlogCard.astro';
|
||||
|
||||
interface CollectionEntry {
|
||||
id: string;
|
||||
data: {
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
categoryColor: string;
|
||||
date: Date;
|
||||
readTime: string;
|
||||
imageUrl: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Props {
|
||||
posts: CollectionEntry[];
|
||||
currentSlug?: string;
|
||||
}
|
||||
|
||||
const { posts, currentSlug } = Astro.props;
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Фильтруем текущую статью
|
||||
const filteredPosts = currentSlug
|
||||
? posts.filter(post => post.id !== currentSlug).slice(0, 3)
|
||||
: posts.slice(0, 3);
|
||||
---
|
||||
|
||||
<section class="related-posts">
|
||||
<h3 class="section-title">Читайте также</h3>
|
||||
|
||||
<div class="related-grid">
|
||||
{filteredPosts.map((post) => (
|
||||
<BlogCard
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
category={post.data.category}
|
||||
categoryColor={post.data.categoryColor}
|
||||
date={formatDate(post.data.date)}
|
||||
readTime={post.data.readTime}
|
||||
imageUrl={post.data.imageUrl}
|
||||
slug={`/blog/${post.id}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style is:global>
|
||||
.related-posts {
|
||||
margin-top: 3rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 2px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: #1e293b;
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
font-weight: 800;
|
||||
margin: 0 0 2rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.related-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.related-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.related-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
20
frontend/src/content.config.ts
Normal file
20
frontend/src/content.config.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { z } from 'astro/zod';
|
||||
import { defineCollection } from 'astro:content';
|
||||
import { glob } from 'astro/loaders';
|
||||
|
||||
const blog = defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
author: z.string().default('Юрист АВ'),
|
||||
category: z.string(),
|
||||
categoryColor: z.string().default('bg-gold'),
|
||||
date: z.date(),
|
||||
readTime: z.string(),
|
||||
imageUrl: z.string(),
|
||||
draft: z.boolean().default(false),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog };
|
||||
40
frontend/src/content/blog/camera-fine-appeal.mdx
Normal file
40
frontend/src/content/blog/camera-fine-appeal.mdx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
title: "Спор с ГИБДД: как обжаловать штраф с камеры"
|
||||
description: "Камеры фотофиксации часто ошибаются. Рассказываем, как правильно обжаловать штраф, полученные с автоматических комплексов."
|
||||
author: "Юрист АВ"
|
||||
category: "Штрафы"
|
||||
categoryColor: "bg-green"
|
||||
date: 2024-03-05
|
||||
readTime: "7 мин"
|
||||
imageUrl: "/images/blog/camera-fine.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Автоматические камеры фиксации нарушений не всегда работают корректно. Ошибки могут быть связаны с неправильной настройкой, погодными условиями и другими факторами.
|
||||
|
||||
## Типичные ошибки камер
|
||||
|
||||
- Неверное определение номера автомобиля
|
||||
- Фиксация нарушения на соседней полосе
|
||||
- Ошибки в распознавании дорожных знаков
|
||||
- Срабатывание на тени или отражении
|
||||
|
||||
## Порядок обжалования
|
||||
|
||||
Подать жалобу можно в течение **10 дней** через:
|
||||
|
||||
- Портал Госуслуг
|
||||
- Лично в подразделении ГИБДД
|
||||
- Через суд
|
||||
|
||||
**Необходимые документы:**
|
||||
|
||||
1. Копия постановления о штрафе
|
||||
2. Доказательства вашей невиновности (видео, фото, свидетели)
|
||||
3. Заявление с обоснованием жалобы
|
||||
|
||||
## Заключение
|
||||
|
||||
Не оплачивайте штрафы, с которыми не согласны. Обжалуйте их в установленный срок.
|
||||
42
frontend/src/content/blog/car-dealer-dispute.mdx
Normal file
42
frontend/src/content/blog/car-dealer-dispute.mdx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: "Спор с автосалоном: как вернуть неисправный автомобиль"
|
||||
description: "Права потребителя при покупке автомобиля с дефектами. Закон «О защите прав потребителей» и судебная практика в Сургуте."
|
||||
author: "Юрист АВ"
|
||||
category: "Автосалоны"
|
||||
categoryColor: "bg-gold"
|
||||
date: 2024-02-20
|
||||
readTime: "11 мин"
|
||||
imageUrl: "/images/blog/car-dealer-dispute.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Покупка автомобиля — серьёзное вложение средств. Что делать, если купленная машина оказалась с дефектами?
|
||||
|
||||
## Права потребителя
|
||||
|
||||
По закону вы вправе:
|
||||
|
||||
- Вернуть автомобиль в течение 15 дней после покупки
|
||||
- Потребовать соразмерного уменьшения цены
|
||||
- Потребовать безвозмездного устранения недостатков
|
||||
|
||||
## Экспертиза качества
|
||||
|
||||
Проведение независимой экспертизы поможет подтвердить наличие дефектов и их стоимость устранения.
|
||||
|
||||
**Важно:** Автосалон обязан принять автомобиль на экспертизу за свой счёт, если гарантийный срок ещё не истёк.
|
||||
|
||||
## Судебная практика
|
||||
|
||||
Суды в большинстве случаев встают на сторону потребителей. Однако для успеха необходимо:
|
||||
|
||||
1. Сохранить все документы о покупке
|
||||
2. Зафиксировать дефекты актом
|
||||
3. Провести независимую экспертизу
|
||||
4. Подать претензию в автосалон
|
||||
|
||||
## Заключение
|
||||
|
||||
Не бойтесь отстаивать свои права. Закон на стороне потребителя, а юристы помогут вам в этом.
|
||||
59
frontend/src/content/blog/dtp-instruction-2024.mdx
Normal file
59
frontend/src/content/blog/dtp-instruction-2024.mdx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
title: "Что делать при ДТП: пошаговая инструкция 2024"
|
||||
description: "Подробный разбор действий после дорожно-транспортного происшествия. Как оформить ДТП, какие документы собрать и куда обращаться за компенсацией."
|
||||
author: "Юрист АВ"
|
||||
category: "ДТП"
|
||||
categoryColor: "bg-red"
|
||||
date: 2024-03-20
|
||||
readTime: "8 мин"
|
||||
imageUrl: "/images/posts/2026/04/dtp-instruction.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Дорожно-транспортное происшествие — это всегда стрессовая ситуация для любого водителя. Однако от правильности ваших действий в первые минуты после аварии зависит не только безопасность всех участников, но и успешность получения страховой выплаты.
|
||||
|
||||
## Первые действия после ДТП
|
||||
|
||||
Немедленно остановите автомобиль, включите аварийную сигнализацию и выставьте знак аварийной остановки. Не перемещайте предметы, имеющие отношение к ДТП, до прибытия сотрудников ГИБДД.
|
||||
|
||||
**Основные шаги:**
|
||||
|
||||
1. Остановитесь и не покидайте место происшествия
|
||||
2. Включите аварийную сигнализацию
|
||||
3. Выставьте знак аварийной остановки (15 м в населённом пункте, 30 м вне его)
|
||||
4. Проверьте состояние всех участников ДТП
|
||||
5. При необходимости вызовите скорую помощь
|
||||
|
||||
## Оформление документов
|
||||
|
||||
При оформлении ДТП по европротоколу (без вызова ГИБДД) необходимо заполнить извещение о ДТП. Оба участника должны подписать документ и сфотографировать место происшествия.
|
||||
|
||||
> **Важно:** Если второй участник ДТП предлагает разобраться на месте без оформления — не соглашайтесь. Это может привести к серьёзным проблемам в будущем.
|
||||
|
||||
### Когда вызов ГИБДД обязателен
|
||||
|
||||
- Если есть пострадавшие
|
||||
- Если ущерб превышает 100 000 рублей
|
||||
- Если у одного из участников нет ОСАГО
|
||||
- Если возникли разногласия между участниками
|
||||
|
||||
## Обращение в страховую компанию
|
||||
|
||||
В течение 5 рабочих дней после ДТП обратитесь в свою страховую компанию с заявлением и полным пакетом документов. Задержка может стать основанием для отказа в выплате.
|
||||
|
||||
**Необходимые документы:**
|
||||
|
||||
- Заявление о страховой выплате
|
||||
- Извещение о ДТП (европротокол) или справка о ДТП
|
||||
- Копия протокола об административном правонарушении (если составлялся)
|
||||
- Копия постановления по делу об административном правонарушении
|
||||
- Паспорт транспортного средства
|
||||
- Свидетельство о регистрации ТС
|
||||
|
||||
## Заключение
|
||||
|
||||
Своевременное и правильное оформление ДТП — залог успешного получения страховой выплаты. Если у вас возникли сложности, наши юристы готовы помочь вам разобраться в ситуации.
|
||||
|
||||
**Помните:** профессиональная юридическая помощь значительно повышает шансы на благоприятный исход дела.
|
||||
37
frontend/src/content/blog/independent-expertise.mdx
Normal file
37
frontend/src/content/blog/independent-expertise.mdx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: "Независимая экспертиза после ДТП: зачем и когда нужна"
|
||||
description: "Когда страховая занижает ущерб — поможет независимая оценка. Как выбрать эксперта, сколько стоит и как использовать в суде."
|
||||
author: "Юрист АВ"
|
||||
category: "ДТП"
|
||||
categoryColor: "bg-red"
|
||||
date: 2024-02-15
|
||||
readTime: "6 мин"
|
||||
imageUrl: "/images/blog/independent-expertise.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Независимая экспертиза — важный инструмент для защиты ваших интересов при получении страховой выплаты.
|
||||
|
||||
## Когда нужна экспертиза
|
||||
|
||||
- Сумма выплаты существенно отличается от реального ущерба
|
||||
- Страховая затягивает сроки рассмотрения
|
||||
- Есть споры о степени повреждений
|
||||
- Не учтены скрытые повреждения
|
||||
|
||||
## Выбор эксперта
|
||||
|
||||
Обращайтесь только к сертифицированным оценщикам, имеющим лицензию на данный вид деятельности.
|
||||
|
||||
**На что обратить внимание:**
|
||||
|
||||
1. Наличие сертификата и лицензии
|
||||
2. Опыт работы (желательно от 3 лет)
|
||||
3. Отзывы клиентов
|
||||
4. Стоимость услуг
|
||||
|
||||
## Заключение
|
||||
|
||||
Независимая экспертиза — ваш козырь в споре со страховой компанией.
|
||||
53
frontend/src/content/blog/license-appeal.mdx
Normal file
53
frontend/src/content/blog/license-appeal.mdx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: "Как оспорить лишение водительских прав"
|
||||
description: "Разбираем основные основания для лишения прав и способы защиты в суде. Сроки обжалования, необходимые документы и типичные ошибки водителей."
|
||||
author: "Юрист АВ"
|
||||
category: "Лишение прав"
|
||||
categoryColor: "bg-blue"
|
||||
date: 2024-03-15
|
||||
readTime: "12 мин"
|
||||
imageUrl: "/images/blog/license-appeal.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Лишение водительских прав — серьёзное наказание, которое может существенно осложнить жизнь. Однако не все водители знают, что решение суда можно обжаловать.
|
||||
|
||||
## Основания для лишения прав
|
||||
|
||||
Основные причины лишения:
|
||||
|
||||
- Управление в состоянии опьянения
|
||||
- Отказ от медицинского освидетельствования
|
||||
- Оставление места ДТП
|
||||
- Повторные нарушения ПДД
|
||||
- Выезд на встречную полосу
|
||||
|
||||
## Сроки обжалования
|
||||
|
||||
На обжалование решения суда даётся **10 дней** с момента вынесения постановления. Пропуск срока возможен только по уважительным причинам.
|
||||
|
||||
> **Важно:** Срок обжалования начинает течь с момента получения копии постановления. Если вы не получили документ — срок не начинается.
|
||||
|
||||
## Необходимые документы
|
||||
|
||||
Для успешного обжалования потребуются:
|
||||
|
||||
1. Копия постановления суда
|
||||
2. Протокол об административном правонарушении
|
||||
3. Показания свидетелей (письменные)
|
||||
4. Видео- и фотоматериалы с места происшествия
|
||||
5. Заключения экспертов (при необходимости)
|
||||
|
||||
## Типичные ошибки
|
||||
|
||||
Многие водители допускают следующие ошибки:
|
||||
|
||||
- **Пропуск срока обжалования** — не ждите до последнего дня
|
||||
- **Отсутствие адвоката** — самостоятельная защита не всегда эффективна
|
||||
- **Игнорирование судебныхных заседаний** — обязательно присутствуйте
|
||||
|
||||
## Заключение
|
||||
|
||||
Обжалование лишения прав — сложный процесс, требующий профессионального подхода. Обратитесь к специалистам для повышения шансов на успех.
|
||||
45
frontend/src/content/blog/license-return-2024.mdx
Normal file
45
frontend/src/content/blog/license-return-2024.mdx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: "Возврат прав после лишения: новая процедура"
|
||||
description: "Изменения в законодательстве 2024 года. Новый порядок возврата водительского удостоверения после окончания срока лишения."
|
||||
author: "Юрист АВ"
|
||||
category: "Лишение прав"
|
||||
categoryColor: "bg-blue"
|
||||
date: 2024-02-28
|
||||
readTime: "9 мин"
|
||||
imageUrl: "/images/blog/license-return.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
В 2024 году вступили в силу изменения в порядке возврата водительских прав после лишения.
|
||||
|
||||
## Что изменилось
|
||||
|
||||
Теперь для возврата прав необходимо:
|
||||
|
||||
- Повторно сдать теоретический экзамен по ПДД
|
||||
- Оплатить все штрафы
|
||||
- Предоставить медицинскую справку (для некоторых категорий нарушений)
|
||||
|
||||
## Медицинская справка
|
||||
|
||||
Для некоторых категорий нарушений требуется предоставление медицинской справки об отсутствии противопоказаний к управлению ТС.
|
||||
|
||||
**Когда нужна медсправка:**
|
||||
|
||||
- Лишение за управление в состоянии опьянения
|
||||
- Отказ от медицинского освидетельствования
|
||||
- Лишение за повторные нарушения
|
||||
|
||||
## Пошаговая инструкция
|
||||
|
||||
1. Дождитесь окончания срока лишения
|
||||
2. Подготовьте необходимые документы
|
||||
3. Сдайте экзамен по ПДД
|
||||
4. Обратитесь в подразделение ГИБДД
|
||||
5. Получите водительское удостоверение
|
||||
|
||||
## Заключение
|
||||
|
||||
Процедура возврата прав стала более регламентированной. Соблюдайте все требования для успешного получения удостоверения.
|
||||
34
frontend/src/content/blog/no-osago-at-fault.mdx
Normal file
34
frontend/src/content/blog/no-osago-at-fault.mdx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: "Что делать, если виновник ДТП не имеет ОСАГО"
|
||||
description: "Как получить компенсацию, если у виновника аварии нет полиса ОСАГО. Судебный иск, взыскание ущерба и практические советы юриста."
|
||||
author: "Юрист АВ"
|
||||
category: "ОСАГО"
|
||||
categoryColor: "bg-gold"
|
||||
date: 2024-02-08
|
||||
readTime: "8 мин"
|
||||
imageUrl: "/images/blog/no-osago.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Ситуация, когда виновник не имеет полиса ОСАГО, встречается нередко. Разбираемся, как получить компенсацию.
|
||||
|
||||
## Возмещение через суд
|
||||
|
||||
Если у виновника нет ОСАГО, единственный способ получения компенсации — обращение в суд с иском к виновнику.
|
||||
|
||||
**Необходимые документы:**
|
||||
|
||||
1. Исковое заявление
|
||||
2. Документы о ДТП (протокол, справка)
|
||||
3. Заключение независимой экспертизы
|
||||
4. Документы о стоимости ремонта
|
||||
|
||||
## Исполнительное производство
|
||||
|
||||
После решения суда можно обратиться к судебным приставам для принудительного взыскания.
|
||||
|
||||
## Заключение
|
||||
|
||||
Не оставляйте попыток получить компенсацию. Даже если у виновника нет ОСАГО, закон на вашей стороне.
|
||||
46
frontend/src/content/blog/osago-full-payout.mdx
Normal file
46
frontend/src/content/blog/osago-full-payout.mdx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "ОСАГО: как получить полную выплату от страховой"
|
||||
description: "Почему страховые компании занижают выплаты и как добиться справедливой компенсации. Независимая экспертиза и судебная практика."
|
||||
author: "Юрист АВ"
|
||||
category: "ОСАГО"
|
||||
categoryColor: "bg-gold"
|
||||
date: 2024-03-10
|
||||
readTime: "10 мин"
|
||||
imageUrl: "/images/blog/osago-payout.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Страховые компании часто занижают размер выплат по ОСАГО. Разбираемся, как добиться справедливой компенсации.
|
||||
|
||||
## Почему страховая занижает выплату
|
||||
|
||||
Основные причины:
|
||||
|
||||
- Использование минимальных коэффициентов износа
|
||||
- Применение расценок не из вашего региона
|
||||
- Игнорирование скрытых повреждений
|
||||
- Неучтённые дополнительные расходы (эвакуация, хранение)
|
||||
|
||||
## Независимая экспертиза
|
||||
|
||||
Если вы не согласны с суммой выплаты, проведите независимую оценку ущерба. Это будет весомым аргументом при обращении в суд.
|
||||
|
||||
**Шаги для проведения экспертизы:**
|
||||
|
||||
1. Найдите сертифицированного эксперта-оценщика
|
||||
2. Согласуйте дату и место осмотра
|
||||
3. Уведомите страховую компанию о проведении экспертизы
|
||||
4. Получите заключение эксперта
|
||||
5. Подайте претензию в страховую компанию
|
||||
|
||||
> **Совет:** Расходы на независимую экспертизу можно взыскать со страховой компании через суд.
|
||||
|
||||
## Обращение в суд
|
||||
|
||||
Если страховая отказывается доплачивать, обращайтесь в суд. Практика показывает, что в большинстве случаев суды встают на сторону автовладельцев.
|
||||
|
||||
## Заключение
|
||||
|
||||
Не соглашайтесь на заниженные выплаты. Боритесь за свои права с помощью профессиональных юристов.
|
||||
31
frontend/src/content/blog/protocol-errors.mdx
Normal file
31
frontend/src/content/blog/protocol-errors.mdx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
title: "Обжалование протокола ГИБДД: типичные ошибки инспекторов"
|
||||
description: "Какие нарушения допускают сотрудники ГИБДД при составлении протокола и как использовать это в свою пользу при обжаловании."
|
||||
author: "Юрист АВ"
|
||||
category: "Штрафы"
|
||||
categoryColor: "bg-green"
|
||||
date: 2024-02-01
|
||||
readTime: "10 мин"
|
||||
imageUrl: "/images/blog/protocol-errors.avif"
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
Протокол ГИБДД может быть признан недействительным при наличии существенных нарушений в порядке его составления.
|
||||
|
||||
## Типичные нарушения
|
||||
|
||||
- Отсутствие понятых
|
||||
- Неправильное указание данных
|
||||
- Отсутствие схемы ДТП
|
||||
- Нарушения при фотофиксации
|
||||
- Протокол составлен не тем должностным лицом
|
||||
|
||||
## Как обжаловать
|
||||
|
||||
Подайте жалобу в вышестоящий орган или суд в течение **10 дней** с момента получения копии протокола.
|
||||
|
||||
## Заключение
|
||||
|
||||
Знание своих прав и процедур обжалования — ключ к успешной защите интересов.
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
export interface BlogPost {
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
categoryColor: string;
|
||||
date: string;
|
||||
readTime: string;
|
||||
imageUrl: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export const blogPosts: BlogPost[] = [
|
||||
{
|
||||
title: "Что делать при ДТП: пошаговая инструкция 2024",
|
||||
description: "Подробный разбор действий после дорожно-транспортного происшествия. Как оформить ДТП, какие документы собрать и куда обращаться за компенсацией.",
|
||||
category: "ДТП",
|
||||
categoryColor: "bg-red",
|
||||
date: "2024-03-20",
|
||||
readTime: "8 мин",
|
||||
imageUrl: "/images/blog/dtp-instruction.avif",
|
||||
slug: "/blog/dtp-instruction-2024"
|
||||
},
|
||||
{
|
||||
title: "Как оспорить лишение водительских прав",
|
||||
description: "Разбираем основные основания для лишения прав и способы защиты в суде. Сроки обжалования, необходимые документы и типичные ошибки водителей.",
|
||||
category: "Лишение прав",
|
||||
categoryColor: "bg-blue",
|
||||
date: "2024-03-15",
|
||||
readTime: "12 мин",
|
||||
imageUrl: "/images/blog/license-appeal.avif",
|
||||
slug: "/blog/license-appeal"
|
||||
},
|
||||
{
|
||||
title: "ОСАГО: как получить полную выплату от страховой",
|
||||
description: "Почему страховые компании занижают выплаты и как добиться справедливой компенсации. Независимая экспертиза и судебная практика.",
|
||||
category: "ОСАГО",
|
||||
categoryColor: "bg-gold",
|
||||
date: "2024-03-10",
|
||||
readTime: "10 мин",
|
||||
imageUrl: "/images/blog/osago-payout.avif",
|
||||
slug: "/blog/osago-full-payout"
|
||||
},
|
||||
{
|
||||
title: "Спор с ГИБДД: как обжаловать штраф с камеры",
|
||||
description: "Камеры фотофиксации часто ошибаются. Рассказываем, как правильно обжаловать штраф, полученные с автоматических комплексов.",
|
||||
category: "Штрафы",
|
||||
categoryColor: "bg-green",
|
||||
date: "2024-03-05",
|
||||
readTime: "7 мин",
|
||||
imageUrl: "/images/blog/camera-fine.avif",
|
||||
slug: "/blog/camera-fine-appeal"
|
||||
},
|
||||
{
|
||||
title: "Возврат прав после лишения: новая процедура",
|
||||
description: "Изменения в законодательстве 2024 года. Новый порядок возврата водительского удостоверения после окончания срока лишения.",
|
||||
category: "Лишение прав",
|
||||
categoryColor: "bg-blue",
|
||||
date: "2024-02-28",
|
||||
readTime: "9 мин",
|
||||
imageUrl: "/images/blog/license-return.avif",
|
||||
slug: "/blog/license-return-2024"
|
||||
},
|
||||
{
|
||||
title: "Спор с автосалоном: как вернуть неисправный автомобиль",
|
||||
description: "Права потребителя при покупке автомобиля с дефектами. Закон «О защите прав потребителей» и судебная практика в Сургуте.",
|
||||
category: "Автосалоны",
|
||||
categoryColor: "bg-gold",
|
||||
date: "2024-02-20",
|
||||
readTime: "11 мин",
|
||||
imageUrl: "/images/blog/car-dealer-dispute.avif",
|
||||
slug: "/blog/car-dealer-dispute"
|
||||
},
|
||||
{
|
||||
title: "Независимая экспертиза после ДТП: зачем и когда нужна",
|
||||
description: "Когда страховая занижает ущерб — поможет независимая оценка. Как выбрать эксперта, сколько стоит и как использовать в суде.",
|
||||
category: "ДТП",
|
||||
categoryColor: "bg-red",
|
||||
date: "2024-02-15",
|
||||
readTime: "6 мин",
|
||||
imageUrl: "/images/blog/independent-expertise.avif",
|
||||
slug: "/blog/independent-expertise"
|
||||
},
|
||||
{
|
||||
title: "Что делать, если виновник ДТП не имеет ОСАГО",
|
||||
description: "Как получить компенсацию, если у виновника аварии нет полиса ОСАГО. Судебный иск, взыскание ущерба и практические советы юриста.",
|
||||
category: "ОСАГО",
|
||||
categoryColor: "bg-gold",
|
||||
date: "2024-02-08",
|
||||
readTime: "8 мин",
|
||||
imageUrl: "/images/blog/no-osago.avif",
|
||||
slug: "/blog/no-osago-at-fault"
|
||||
},
|
||||
{
|
||||
title: "Обжалование протокола ГИБДД: типичные ошибки инспекторов",
|
||||
description: "Какие нарушения допускают сотрудники ГИБДД при составлении протокола и как использовать это в свою пользу при обжаловании.",
|
||||
category: "Штрафы",
|
||||
categoryColor: "bg-green",
|
||||
date: "2024-02-01",
|
||||
readTime: "10 мин",
|
||||
imageUrl: "/images/blog/protocol-errors.avif",
|
||||
slug: "/blog/protocol-errors"
|
||||
}
|
||||
];
|
||||
|
||||
export const categories = ['Все', 'ДТП', 'ОСАГО', 'Лишение прав', 'Штрафы', 'Автосалоны'];
|
||||
344
frontend/src/layouts/ArticleLayout.astro
Normal file
344
frontend/src/layouts/ArticleLayout.astro
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
---
|
||||
import "@styles/global.css";
|
||||
import { SITE_TITLE_SUFFIX } from "@constants";
|
||||
|
||||
import Header from "@components/layout/header/Header.astro";
|
||||
import Footer from "@components/layout/footer/Footer.astro";
|
||||
import Breadcrumbs from "@components/base/Breadcrumbs.astro";
|
||||
import ConsultationModal from "@components/base/ConsultationModal.astro";
|
||||
import PostSocialShare from "@components/blog/PostSocialShare.astro";
|
||||
import PostReactionButtons from "@components/blog/PostReactionButtons.astro";
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
canonicalLink?: string;
|
||||
breadcrumbs?: Array<{ label: string; href?: string }>;
|
||||
heroImage: string;
|
||||
heroAlt: string;
|
||||
category: string;
|
||||
postTitle: string;
|
||||
date: string;
|
||||
author: string;
|
||||
readTime: string;
|
||||
postId: string;
|
||||
postUrl: string;
|
||||
initialLikes?: number;
|
||||
initialDislikes?: number;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
canonicalLink,
|
||||
breadcrumbs,
|
||||
heroImage,
|
||||
heroAlt,
|
||||
category,
|
||||
postTitle,
|
||||
date,
|
||||
author,
|
||||
readTime,
|
||||
postId,
|
||||
postUrl,
|
||||
initialLikes = 0,
|
||||
initialDislikes = 0
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicons/favicon.svg" />
|
||||
<link rel="icon" href="/favicons/favicon.ico" />
|
||||
<title>{title} {SITE_TITLE_SUFFIX}</title>
|
||||
<meta name="description" content={description} />
|
||||
{canonicalLink && <link rel="canonical" href={canonicalLink} />}
|
||||
<meta name="yandex-verification" content="be3edfd138348e43" />
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
<main class="main-content">
|
||||
{breadcrumbs && breadcrumbs.length > 0 && (
|
||||
<div class="breadcrumbs-wrapper">
|
||||
<Breadcrumbs items={breadcrumbs} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<!-- Hero с полноширинным изображением -->
|
||||
<section class="article-hero">
|
||||
<div class="article-hero-image">
|
||||
<img src={heroImage} alt={heroAlt} />
|
||||
<div class="article-hero-overlay"></div>
|
||||
</div>
|
||||
|
||||
<div class="article-hero-content">
|
||||
<div class="site-container">
|
||||
<div class="article-hero-inner">
|
||||
<div class="article-hero-left">
|
||||
<span class="article-category-badge">{category}</span>
|
||||
<h1 class="article-title">{postTitle}</h1>
|
||||
|
||||
<div class="article-meta">
|
||||
<span class="meta-item">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon">
|
||||
<rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect>
|
||||
<line x1="16" x2="16" y1="2" y2="6"></line>
|
||||
<line x1="8" x2="8" y1="2" y2="6"></line>
|
||||
<line x1="3" x2="21" y1="10" y2="10"></line>
|
||||
</svg>
|
||||
{date}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon">
|
||||
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
<span class="meta-author">{author}</span>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
{readTime}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="article-hero-right">
|
||||
<div class="article-actions">
|
||||
<PostReactionButtons
|
||||
postId={postId}
|
||||
initialLikes={initialLikes}
|
||||
initialDislikes={initialDislikes}
|
||||
/>
|
||||
<PostSocialShare
|
||||
title={postTitle}
|
||||
url={postUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<div class="article-body">
|
||||
<div class="site-container">
|
||||
<div class="article-content-wrapper">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
<ConsultationModal />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
.main-content {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.breadcrumbs-wrapper {
|
||||
padding-top: 4.75rem;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid rgba(30, 48, 80, 0.05);
|
||||
}
|
||||
|
||||
/* Article Hero */
|
||||
.article-hero {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.article-hero-image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.article-hero-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.article-hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, rgba(10, 37, 64, 0.3) 0%, rgba(10, 37, 64, 0.85) 100%);
|
||||
}
|
||||
|
||||
.article-hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.article-hero-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.article-hero-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.article-category-badge {
|
||||
display: inline-block;
|
||||
padding: 0.35rem 0.75rem;
|
||||
background: linear-gradient(135deg, #d4af37 0%, #eac26e 100%);
|
||||
color: #1e293b;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
color: #ffffff;
|
||||
font-size: clamp(1.75rem, 4vw, 3rem);
|
||||
font-weight: 800;
|
||||
margin: 0 0 1.5rem;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.meta-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.meta-author {
|
||||
color: #eac26e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.article-hero-right {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.article-actions {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Article Body */
|
||||
.article-body {
|
||||
padding: 3rem 0;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.article-content-wrapper {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border-radius: 1.5rem;
|
||||
padding: 3rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.article-content-wrapper > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.article-hero-inner {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.article-hero-right {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.article-hero {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.article-hero-content {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.article-actions {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.article-content-wrapper {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Клиентский скрипт для открытия модального окна
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const btn = document.getElementById("consultation-btn");
|
||||
|
||||
btn?.addEventListener("click", () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("open-modal", {
|
||||
detail: "consultation-modal",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Для Astro View Transitions
|
||||
document.addEventListener("astro:page-load", () => {
|
||||
const btn = document.getElementById("consultation-btn");
|
||||
|
||||
btn?.addEventListener("click", () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("open-modal", {
|
||||
detail: "consultation-modal",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
168
frontend/src/pages/blog/[slug].astro
Normal file
168
frontend/src/pages/blog/[slug].astro
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
import ArticleLayout from '@layouts/ArticleLayout.astro';
|
||||
import { SITE_URL } from '@constants';
|
||||
import PostCommentForm from '@components/blog/PostCommentForm.astro';
|
||||
import RelatedPosts from '@components/blog/RelatedPosts.astro';
|
||||
import { getCollection, getEntry, render } from 'astro:content';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('blog') as { id: string; data: Record<string, any> }[];
|
||||
return posts.map((post: { id: string }) => ({
|
||||
params: { slug: post.id },
|
||||
}));
|
||||
}
|
||||
|
||||
const slug = Astro.params.slug;
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect('/blog');
|
||||
}
|
||||
|
||||
const post = await getEntry('blog', slug);
|
||||
|
||||
if (!post) {
|
||||
return Astro.redirect('/blog');
|
||||
}
|
||||
|
||||
const { Content } = await render(post);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Логика авторизации (пока статичная переменная)
|
||||
const isAuthorized = false;
|
||||
|
||||
// URL текущей страницы
|
||||
const currentUrl = `${SITE_URL}/blog/${post.id}`;
|
||||
|
||||
// Получаем все посты для блока "Читайте также"
|
||||
const allPosts = await getCollection('blog');
|
||||
---
|
||||
|
||||
<ArticleLayout
|
||||
title={`${post.data.title} — автоюрист в Сургуте`}
|
||||
description={post.data.description}
|
||||
canonicalLink={`${SITE_URL}/blog/${post.id}`}
|
||||
breadcrumbs={[
|
||||
{ label: 'Главная', href: '/' },
|
||||
{ label: 'Блог', href: '/blog' },
|
||||
{ label: post.data.title }
|
||||
]}
|
||||
heroImage={post.data.imageUrl}
|
||||
heroAlt={post.data.title}
|
||||
category={post.data.category}
|
||||
postTitle={post.data.title}
|
||||
date={formatDate(post.data.date)}
|
||||
author={post.data.author}
|
||||
readTime={post.data.readTime}
|
||||
postId={post.id}
|
||||
postUrl={currentUrl}
|
||||
initialLikes={12}
|
||||
initialDislikes={2}
|
||||
>
|
||||
<!-- Содержимое статьи -->
|
||||
<div class="post-content">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
<!-- Форма комментариев -->
|
||||
<PostCommentForm
|
||||
postId={post.id}
|
||||
isAuthorized={isAuthorized}
|
||||
/>
|
||||
|
||||
<!-- Похожие статьи -->
|
||||
<RelatedPosts
|
||||
posts={allPosts}
|
||||
currentSlug={post.id}
|
||||
/>
|
||||
</ArticleLayout>
|
||||
|
||||
<style>
|
||||
/* Post Content */
|
||||
.post-content {
|
||||
padding: 3rem;
|
||||
color: #334155;
|
||||
line-height: 1.8;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.post-content :global(h2) {
|
||||
color: #1e293b;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin: 2rem 0 1rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.post-content :global(h3) {
|
||||
color: #1e293b;
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
margin: 1.75rem 0 1rem;
|
||||
}
|
||||
|
||||
.post-content :global(p) {
|
||||
margin: 0 0 1.25rem;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.post-content :global(ul), .post-content :global(ol) {
|
||||
margin: 1rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.post-content :global(li) {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.post-content :global(blockquote) {
|
||||
margin: 2rem 0;
|
||||
padding: 1.5rem 2rem;
|
||||
background: #f8fafc;
|
||||
border-left: 4px solid #d4af37;
|
||||
border-radius: 0 0.75rem 0.75rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.post-content :global(blockquote p) {
|
||||
margin: 0;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.post-content :global(strong) {
|
||||
color: #1e293b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.post-content :global(code) {
|
||||
background: #f1f5f9;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.post-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.post-content :global(h2) {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.post-content :global(h3) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,19 +4,35 @@ import { SITE_URL } from '@constants';
|
|||
import PageHero from '@components/base/PageHero.astro';
|
||||
import BlogCategories from '@components/blog/BlogCategories.astro';
|
||||
import BlogCard from '@components/blog/BlogCard.astro';
|
||||
import BlogPagination from '@components/blog/BlogPagination.astro';
|
||||
import Pagination from '@components/base/Pagination.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import SearchModal from '@components/base/SearchModal.astro';
|
||||
import { blogPosts, categories } from '@data/blogData';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
|
||||
// Сортируем посты по дате (новые сверху)
|
||||
const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||
|
||||
const POSTS_PER_PAGE = 6;
|
||||
const currentPage = 1;
|
||||
const totalPages = Math.ceil(blogPosts.length / POSTS_PER_PAGE);
|
||||
const totalPages = Math.ceil(sortedPosts.length / POSTS_PER_PAGE);
|
||||
|
||||
const startIndex = 0;
|
||||
const endIndex = POSTS_PER_PAGE;
|
||||
const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
||||
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Категории
|
||||
const categories = ['Все', ...new Set(posts.map((post: any) => post.data.category))];
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
@ -49,17 +65,17 @@ const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
|||
<section class="blog-grid-section">
|
||||
<div class="site-container">
|
||||
<div class="blog-grid" id="blog-grid">
|
||||
{paginatedPosts.map((post) => (
|
||||
<article class="blog-card-wrapper" data-category={post.category}>
|
||||
{paginatedPosts.map((post: any) => (
|
||||
<article class="blog-card-wrapper" data-category={post.data.category}>
|
||||
<BlogCard
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
category={post.category}
|
||||
categoryColor={post.categoryColor}
|
||||
date={post.date}
|
||||
readTime={post.readTime}
|
||||
imageUrl={post.imageUrl}
|
||||
slug={post.slug}
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
category={post.data.category}
|
||||
categoryColor={post.data.categoryColor}
|
||||
date={formatDate(post.data.date)}
|
||||
readTime={post.data.readTime}
|
||||
imageUrl={post.data.imageUrl}
|
||||
slug={`/blog/${post.id}`}
|
||||
/>
|
||||
</article>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,35 @@ import { SITE_URL } from '@constants';
|
|||
import PageHero from '@components/base/PageHero.astro';
|
||||
import BlogCategories from '@components/blog/BlogCategories.astro';
|
||||
import BlogCard from '@components/blog/BlogCard.astro';
|
||||
import BlogPagination from '@components/blog/BlogPagination.astro';
|
||||
import Pagination from '@components/base/Pagination.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import SearchModal from '@components/base/SearchModal.astro';
|
||||
import { blogPosts, categories } from '@data/blogData';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
|
||||
// Сортируем посты по дате (новые сверху)
|
||||
const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||
|
||||
const POSTS_PER_PAGE = 6;
|
||||
const currentPage = Number(Astro.params.page) || 1;
|
||||
const totalPages = Math.ceil(blogPosts.length / POSTS_PER_PAGE);
|
||||
const totalPages = Math.ceil(sortedPosts.length / POSTS_PER_PAGE);
|
||||
|
||||
const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
|
||||
const endIndex = startIndex + POSTS_PER_PAGE;
|
||||
const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
||||
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Категории
|
||||
const categories = ['Все', ...new Set(posts.map(post => post.data.category))];
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
@ -50,17 +66,17 @@ const paginatedPosts = blogPosts.slice(startIndex, endIndex);
|
|||
<section class="blog-grid-section">
|
||||
<div class="site-container">
|
||||
<div class="blog-grid" id="blog-grid">
|
||||
{paginatedPosts.map((post) => (
|
||||
<article class="blog-card-wrapper" data-category={post.category}>
|
||||
{paginatedPosts.map((post: any) => (
|
||||
<article class="blog-card-wrapper" data-category={post.data.category}>
|
||||
<BlogCard
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
category={post.category}
|
||||
categoryColor={post.categoryColor}
|
||||
date={post.date}
|
||||
readTime={post.readTime}
|
||||
imageUrl={post.imageUrl}
|
||||
slug={post.slug}
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
category={post.data.category}
|
||||
categoryColor={post.data.categoryColor}
|
||||
date={formatDate(post.data.date)}
|
||||
readTime={post.data.readTime}
|
||||
imageUrl={post.data.imageUrl}
|
||||
slug={`/blog/${post.id}`}
|
||||
/>
|
||||
</article>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import Layout from '@layouts/Layout.astro';
|
|||
import { SITE_URL } from '@constants';
|
||||
import BlogCard from '@components/blog/BlogCard.astro';
|
||||
import SearchModal from '@components/base/SearchModal.astro';
|
||||
import { blogPosts } from '@data/blogData';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
|
||||
// Получаем параметр поиска из URL
|
||||
const url = new URL(Astro.request.url);
|
||||
|
|
@ -16,21 +18,30 @@ const breadcrumbsItems = [
|
|||
];
|
||||
|
||||
// Функция поиска по статьям
|
||||
function searchArticles(query: string) {
|
||||
function searchArticles(query: string, allPosts: typeof posts) {
|
||||
if (!query.trim()) return [];
|
||||
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
return blogPosts.filter(post => {
|
||||
const titleMatch = post.title.toLowerCase().includes(lowerQuery);
|
||||
const descriptionMatch = post.description.toLowerCase().includes(lowerQuery);
|
||||
const categoryMatch = post.category.toLowerCase().includes(lowerQuery);
|
||||
|
||||
|
||||
return allPosts.filter(post => {
|
||||
const titleMatch = post.data.title.toLowerCase().includes(lowerQuery);
|
||||
const descriptionMatch = post.data.description.toLowerCase().includes(lowerQuery);
|
||||
const categoryMatch = post.data.category.toLowerCase().includes(lowerQuery);
|
||||
|
||||
return titleMatch || descriptionMatch || categoryMatch;
|
||||
});
|
||||
}
|
||||
|
||||
const searchResults = searchArticles(searchQuery);
|
||||
const searchResults = searchArticles(searchQuery, posts);
|
||||
|
||||
// Форматируем дату
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
|
@ -46,20 +57,20 @@ const searchResults = searchArticles(searchQuery);
|
|||
<h1 class="search-title">
|
||||
{searchQuery ? `Результаты поиска` : 'Поиск статей'}
|
||||
</h1>
|
||||
|
||||
|
||||
{searchQuery && (
|
||||
<div class="search-info">
|
||||
<p class="search-query-text">
|
||||
По запросу: <span class="query-highlight">"{searchQuery}"</span>
|
||||
</p>
|
||||
<p class="search-count">
|
||||
{searchResults.length === 0
|
||||
? 'Ничего не найдено'
|
||||
{searchResults.length === 0
|
||||
? 'Ничего не найдено'
|
||||
: `Найдено статей: ${searchResults.length}`}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<button class="open-search-btn" id="open-search-btn" data-modal-target="search-modal">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
|
|
@ -76,16 +87,16 @@ const searchResults = searchArticles(searchQuery);
|
|||
<section class="results-section">
|
||||
<div class="site-container">
|
||||
<div class="results-grid">
|
||||
{searchResults.map((post) => (
|
||||
{searchResults.map((post: any) => (
|
||||
<BlogCard
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
category={post.category}
|
||||
categoryColor={post.categoryColor}
|
||||
date={post.date}
|
||||
readTime={post.readTime}
|
||||
imageUrl={post.imageUrl}
|
||||
slug={post.slug}
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
category={post.data.category}
|
||||
categoryColor={post.data.categoryColor}
|
||||
date={formatDate(post.data.date)}
|
||||
readTime={post.data.readTime}
|
||||
imageUrl={post.data.imageUrl}
|
||||
slug={`/blog/${post.id}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -131,10 +142,10 @@ const searchResults = searchArticles(searchQuery);
|
|||
<script>
|
||||
(function() {
|
||||
const btn = document.getElementById('open-search-btn');
|
||||
|
||||
|
||||
btn?.addEventListener('click', () => {
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
@ -142,11 +153,11 @@ const searchResults = searchArticles(searchQuery);
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const query = urlParams.get('q');
|
||||
|
||||
|
||||
if (!query && btn) {
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
window.dispatchEvent(new CustomEvent('open-modal', {
|
||||
detail: 'search-modal'
|
||||
}));
|
||||
}, 300);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import Layout from '@layouts/Layout.astro';
|
||||
import { SITE_URL, COMPANY } from '@constants';
|
||||
import PageHero from '@components/base/PageHero.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import AuthLockBlock from '@components/base/AuthLockBlock.astro';
|
||||
|
||||
// Логика авторизации (пока статичная переменная)
|
||||
const isAuthorized = false; // Измените на true, чтобы увидеть форму
|
||||
|
|
@ -23,13 +23,13 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
titleGold="с нами"
|
||||
description="Мы всегда на связи и готовы помочь вам в решении автоспоров. Оставьте заявку или позвоните — первая консультация бесплатно."
|
||||
layout="with-image"
|
||||
sideImage="/images/home/avtourist-surgut.avif"
|
||||
sideImage="/images/contacts/conImg.avif"
|
||||
sideImageAlt="Автоюрист Сургут"
|
||||
experienceBadge={{
|
||||
number: "15",
|
||||
text: "МИНУТ НА СВЯЗИ"
|
||||
}}
|
||||
bgImage="/images/home/bg_hero.avif"
|
||||
bgImage="/images/contacts/conBg.avif"
|
||||
/>
|
||||
|
||||
<!-- Карточки контактов -->
|
||||
|
|
@ -159,28 +159,16 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
</p>
|
||||
</form>
|
||||
) : (
|
||||
<div class="auth-lock-card animate-on-scroll" data-animation="fade-up" data-delay="200">
|
||||
<div class="lock-icon-container">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lock-icon">
|
||||
<rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="lock-title">Форма доступна только клиентам</h3>
|
||||
<p class="lock-text">Чтобы отправить сообщение напрямую юристу, пожалуйста, авторизуйтесь в личном кабинете.</p>
|
||||
<a href="/auth/sign-in" class="auth-button">Войти в кабинет</a>
|
||||
</div>
|
||||
<AuthLockBlock
|
||||
title="Авторизуйтесь, чтобы написать"
|
||||
description="Чтобы отправить сообщение напрямую юристу, пожалуйста, войдите в личный кабинет."
|
||||
buttonText="Войти в кабинет"
|
||||
buttonHref="/auth/sign-in"
|
||||
className="animate-on-scroll"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA-блок -->
|
||||
<CTA
|
||||
icon="phone"
|
||||
title="Нужна срочная помощь?"
|
||||
description="Запишитесь на бесплатную консультацию прямо сейчас — мы перезвоним в течение 15 минут"
|
||||
btnText="Записаться на консультацию"
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
|
|
@ -247,7 +235,7 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
|
||||
.hours-icon { width: 1rem; height: 1rem; }
|
||||
|
||||
/* ===== FORM SECTION & AUTH LOCK ===== */
|
||||
/* ===== FORM SECTION ===== */
|
||||
.contact-form-section {
|
||||
padding: 5rem 0;
|
||||
background: #ffffff;
|
||||
|
|
@ -257,49 +245,6 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
.section-title { font-size: clamp(2rem, 4vw, 2.5rem); font-weight: 800; color: #1e293b; margin: 0 0 1rem; }
|
||||
.section-description { color: #64748b; font-size: 1.1rem; margin: 0; }
|
||||
|
||||
/* Auth Lock Card */
|
||||
.auth-lock-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #f8fafc;
|
||||
border: 2px dashed #e2e8f0;
|
||||
border-radius: 1.5rem;
|
||||
padding: 4rem 2rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lock-icon-container {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
background: #ffffff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.lock-icon { width: 2rem; height: 2rem; }
|
||||
.lock-title { color: #1e293b; font-size: 1.5rem; font-weight: 700; margin: 0 0 1rem; }
|
||||
.lock-text { color: #64748b; line-height: 1.6; margin: 0 0 2rem; max-width: 400px; }
|
||||
|
||||
.auth-button {
|
||||
background: #1e293b;
|
||||
color: #ffffff;
|
||||
padding: 0.875rem 2rem;
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auth-button:hover { background: #0f172a; transform: translateY(-2px); }
|
||||
|
||||
/* Contact Form Styles */
|
||||
.contact-form {
|
||||
max-width: 700px;
|
||||
|
|
@ -359,7 +304,6 @@ const isAuthorized = false; // Измените на true, чтобы увиде
|
|||
@media (max-width: 768px) {
|
||||
.cards-grid { grid-template-columns: 1fr; }
|
||||
.form-row { grid-template-columns: 1fr; }
|
||||
.auth-lock-card { padding: 3rem 1.5rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
import Layout from '@layouts/Layout.astro';
|
||||
import { SITE_URL } from '@constants';
|
||||
import PageHero from '@components/base/PageHero.astro';
|
||||
import CTA from '@components/base/CTA.astro';
|
||||
import Pagination from '@components/base/Pagination.astro';
|
||||
import ReviewCard from '@components/reviews/ReviewCard.astro';
|
||||
import VotingSummary from '@components/reviews/VotingSummary.astro';
|
||||
import AuthLockBlock from '@components/base/AuthLockBlock.astro';
|
||||
import { reviewsData, votingSummary } from '@data/reviewsData';
|
||||
|
||||
// Логика авторизации (пока статичная переменная)
|
||||
const isAuthorized = true; // Измените на true, чтобы увидеть форму
|
||||
|
||||
const REVIEWS_PER_PAGE = 6;
|
||||
const currentPage = 1;
|
||||
const totalPages = Math.ceil(reviewsData.length / REVIEWS_PER_PAGE);
|
||||
|
|
@ -32,13 +35,13 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
titleGold="водителей из Сургута"
|
||||
description="Узнайте, как мы помогли нашим клиентам решить их проблемы с автоспорами"
|
||||
layout="with-image"
|
||||
sideImage="/images/home/avtourist-surgut.avif"
|
||||
sideImage="/images/reviews/revImg.avif"
|
||||
sideImageAlt="Автоюрист Сургут"
|
||||
experienceBadge={{
|
||||
number: "95%",
|
||||
text: "ДОВОЛЬНЫХ КЛИЕНТОВ"
|
||||
}}
|
||||
bgImage="/images/home/bg_hero.avif"
|
||||
bgImage="/images/reviews/revBg.avif"
|
||||
/>
|
||||
|
||||
<section class="reviews-page">
|
||||
|
|
@ -78,14 +81,94 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
/>
|
||||
)}
|
||||
|
||||
<!-- CTA блок -->
|
||||
<CTA
|
||||
icon="chat"
|
||||
title="Хотите оставить отзыв?"
|
||||
description="Поделитесь своим опытом работы с нами. Ваш отзыв поможет другим водителям принять правильное решение."
|
||||
btnText="Оставить отзыв"
|
||||
btnHref="/contacts"
|
||||
/>
|
||||
<!-- Форма для отзыва -->
|
||||
<section class="review-form-section">
|
||||
<div class="site-container">
|
||||
<div class="form-header">
|
||||
<h2 class="section-title animate-on-scroll" data-animation="fade-up">
|
||||
Оставьте <span class="text-gold">отзыв</span>
|
||||
</h2>
|
||||
<p class="section-description animate-on-scroll" data-animation="fade-up" data-delay="100">
|
||||
Поделитесь своим опытом — ваше мнение важно для нас
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isAuthorized ? (
|
||||
<form class="review-form animate-on-scroll" data-animation="fade-up" data-delay="200" action="#" method="POST" id="review-form">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="review-name" class="form-label">Имя *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="review-name"
|
||||
name="name"
|
||||
class="form-input"
|
||||
placeholder="Иван"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="review-surname" class="form-label">Фамилия *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="review-surname"
|
||||
name="surname"
|
||||
class="form-input"
|
||||
placeholder="Иванов"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="review-rating" class="form-label">Оценка *</label>
|
||||
<select
|
||||
id="review-rating"
|
||||
name="rating"
|
||||
class="form-input"
|
||||
required
|
||||
>
|
||||
<option value="">Выберите оценку</option>
|
||||
<option value="5">5 — Отлично</option>
|
||||
<option value="4">4 — Хорошо</option>
|
||||
<option value="3">3 — Удовлетворительно</option>
|
||||
<option value="2">2 — Плохо</option>
|
||||
<option value="1">1 — Очень плохо</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="review-text" class="form-label">Ваш отзыв *</label>
|
||||
<textarea
|
||||
id="review-text"
|
||||
name="review"
|
||||
class="form-textarea"
|
||||
placeholder="Расскажите о вашем опыте работы с нами..."
|
||||
rows="5"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-btn">
|
||||
Отправить отзыв
|
||||
</button>
|
||||
|
||||
<p class="form-privacy">
|
||||
Нажимая кнопку, вы соглашаетесь с
|
||||
<a href="/privacy" class="privacy-link">политикой конфиденциальности</a>
|
||||
</p>
|
||||
</form>
|
||||
) : (
|
||||
<AuthLockBlock
|
||||
title="Авторизуйтесь, чтобы оставить отзыв"
|
||||
description="Чтобы поделиться своим опытом, пожалуйста, войдите в личный кабинет."
|
||||
buttonText="Войти в кабинет"
|
||||
buttonHref="/auth/sign-in"
|
||||
className="animate-on-scroll"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
|
@ -104,48 +187,64 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
/* CTA блок */
|
||||
.cta-section {
|
||||
margin-top: 4rem;
|
||||
padding: 3rem;
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
border-radius: 1.5rem;
|
||||
text-align: center;
|
||||
/* ===== REVIEW FORM SECTION ===== */
|
||||
.review-form-section {
|
||||
padding: 5rem 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
color: #ffffff;
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
font-weight: 800;
|
||||
margin: 0 0 1rem 0;
|
||||
letter-spacing: -0.02em;
|
||||
.form-header { text-align: center; margin-bottom: 3rem; }
|
||||
.section-title { font-size: clamp(2rem, 4vw, 2.5rem); font-weight: 800; color: #1e293b; margin: 0 0 1rem; }
|
||||
.section-description { color: #64748b; font-size: 1.1rem; margin: 0; }
|
||||
|
||||
/* Review Form Styles */
|
||||
.review-form {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.cta-text {
|
||||
color: #cbd5e1;
|
||||
font-size: 1.125rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 2rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.form-label { font-size: 0.9rem; font-weight: 600; color: #1e293b; }
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 1rem 2.5rem;
|
||||
background: linear-gradient(135deg, #d4af37 0%, #fbbf24 100%);
|
||||
color: #1e293b;
|
||||
font-weight: 700;
|
||||
font-size: 1.125rem;
|
||||
text-decoration: none;
|
||||
.form-input, .form-textarea {
|
||||
padding: 0.875rem 1rem;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s ease;
|
||||
outline: none;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.form-input:focus, .form-textarea:focus {
|
||||
border-color: #d4af37;
|
||||
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.1);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.form-textarea { resize: vertical; min-height: 140px; }
|
||||
|
||||
.submit-btn {
|
||||
background: linear-gradient(135deg, #d4af37 0%, #eac26e 100%);
|
||||
color: #1e293b;
|
||||
border: none;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 15px rgba(212, 175, 55, 0.3);
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(212, 175, 55, 0.4);
|
||||
}
|
||||
.submit-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 25px rgba(212, 175, 55, 0.4); }
|
||||
.form-privacy { font-size: 0.8rem; color: #94a3b8; text-align: center; }
|
||||
.privacy-link { color: #d4af37; text-decoration: underline; }
|
||||
|
||||
/* Анимации */
|
||||
.animate-on-scroll {
|
||||
|
|
@ -178,9 +277,7 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
padding: 2rem;
|
||||
}
|
||||
.form-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -213,11 +310,27 @@ const paginatedReviews = reviewsData.slice(startIndex, endIndex);
|
|||
});
|
||||
};
|
||||
|
||||
// Обработка формы отзыва
|
||||
const setupReviewForm = () => {
|
||||
const form = document.getElementById('review-form') as HTMLFormElement;
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
console.log('Отправка отзыва:', Object.fromEntries(formData));
|
||||
alert('Спасибо! Ваш отзыв отправлен на модерацию.');
|
||||
form.reset();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Запуск
|
||||
setupAnimations();
|
||||
setupReviewForm();
|
||||
|
||||
// Для поддержки View Transitions в Astro
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
setupAnimations();
|
||||
setupReviewForm();
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue