add:
- legal .md files
- routers
- home view
- rewrite App.vue
- update footer / header
- update features
This commit is contained in:
austnv
2026-06-07 01:19:02 +03:00
parent eb17a3cd67
commit fa8498ef4c
14 changed files with 435 additions and 45 deletions
+85 -2
View File
@@ -1,24 +1,27 @@
{
"name": "uvpn.shop",
"version": "0.2.0",
"version": "0.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "uvpn.shop",
"version": "0.2.0",
"version": "0.3.1",
"dependencies": {
"@lucide/vue": "^1.16.0",
"@tailwindcss/vite": "^4.3.0",
"@tresjs/cientos": "^5.7.1",
"@tresjs/core": "^5.8.1",
"marked": "^18.0.5",
"swiper": "^12.1.4",
"tailwindcss": "^4.3.0",
"three": "^0.184.0",
"vue": "^3.5.32",
"vue-router": "^4.6.4",
"vue3-simple-icons": "^16.10.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@vitejs/plugin-vue": "^6.0.6",
"vite": "^8.0.8",
"vite-plugin-vue-devtools": "^8.1.1"
@@ -1134,6 +1137,19 @@
"node": ">= 20"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz",
@@ -1665,6 +1681,19 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -2289,6 +2318,18 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/marked": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz",
"integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/meshoptimizer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz",
@@ -2441,6 +2482,20 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/potpack": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
@@ -2799,6 +2854,13 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",
@@ -3001,6 +3063,27 @@
}
}
},
"node_modules/vue-router": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/vue-router/node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/vue3-simple-icons": {
"version": "16.10.0",
"resolved": "https://registry.npmjs.org/vue3-simple-icons/-/vue3-simple-icons-16.10.0.tgz",
+4 -1
View File
@@ -1,6 +1,6 @@
{
"name": "uvpn.shop",
"version": "0.3.1",
"version": "0.4.0",
"private": true,
"type": "module",
"scripts": {
@@ -13,13 +13,16 @@
"@tailwindcss/vite": "^4.3.0",
"@tresjs/cientos": "^5.7.1",
"@tresjs/core": "^5.8.1",
"marked": "^18.0.5",
"swiper": "^12.1.4",
"tailwindcss": "^4.3.0",
"three": "^0.184.0",
"vue": "^3.5.32",
"vue-router": "^4.6.4",
"vue3-simple-icons": "^16.10.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@vitejs/plugin-vue": "^6.0.6",
"vite": "^8.0.8",
"vite-plugin-vue-devtools": "^8.1.1"
+107
View File
@@ -0,0 +1,107 @@
# Пользовательское соглашение
### 1. Общие положения
1.1. Настоящее Пользовательское соглашение (далее — **«Соглашение»** ) регулирует порядок использования онлайн-сервиса (далее — **«Сервис»** ), предоставляемого Администрацией.
1.2. Используя Сервис, включая запуск бота, регистрацию, оплату услуг или получение доступа к материалам, Пользователь подтверждает, что полностью ознакомился с условиями настоящего Соглашения и принимает их в полном объёме.
1.3. В случае несогласия с условиями Соглашения Пользователь обязан прекратить использование Сервиса.
### 2. Характер услуг и цифровых товаров
2.1. Сервис предоставляет цифровые товары и услуги нематериального характера, включая, но не ограничиваясь: информационные материалы, обучающие программы, консультации, цифровые продукты и сервисные услуги.
2.2. Материалы, предоставляемые через Сервис, могут включать:
* информацию из открытых источников;
* авторские материалы Администрации и/или третьих лиц;
* аналитические обзоры, подборки, рекомендации, структурированные данные.
2.3. Пользователь осознаёт и соглашается, что ценность цифровых товаров и услуг Сервиса заключается **в систематизации, анализе, форме подачи, сопровождении, поддержке и обновлениях** , а не в эксклюзивности отдельных фрагментов информации.
2.4. Сервис не заявляет и не гарантирует уникальность, исключительность или недоступность отдельных элементов материалов вне Сервиса.
### 3. Отказ от гарантий и ответственности
3.1. Сервис предоставляется на условиях **«AS IS» («как есть»)** .
3.2. Администрация не гарантирует:
* соответствие Сервиса ожиданиям Пользователя;
* достижение каких-либо финансовых, коммерческих, профессиональных или иных результатов;
* бесперебойную и безошибочную работу Сервиса.
3.3. Администрация не несёт ответственности за:
* любые прямые или косвенные убытки, включая упущенную выгоду;
* последствия применения Пользователем полученных материалов;
* действия или бездействие третьих лиц;
* временные технические сбои и ограничения доступа.
3.4. Все решения о применении материалов, рекомендаций и услуг принимаются Пользователем **самостоятельно и на его риск** .
### 4. Законность использования
4.1. Сервис не предназначен для поощрения, организации или содействия противоправной деятельности.
4.2. Пользователь обязуется использовать Сервис исключительно в рамках применимого законодательства и правил третьих сторон.
4.3. Ответственность за законность использования материалов и услуг Сервиса полностью возлагается на Пользователя.
### 5. Интеллектуальная собственность
5.1. Все материалы, размещённые в Сервисе, охраняются законодательством об интеллектуальной собственности.
5.2. Пользователю запрещается копировать, распространять, перепродавать, передавать третьим лицам или иным образом использовать материалы Сервиса без разрешения правообладателя.
5.3. Нарушение прав интеллектуальной собственности может повлечь ограничение доступа к Сервису без компенсации.
### 6. Ограничение доступа
6.1. Администрация вправе приостановить или ограничить доступ Пользователя к Сервису в случае:
* нарушения условий настоящего Соглашения;
* выявления злоупотреблений;
* требований законодательства или платёжных провайдеров.
6.2. Ограничение доступа не освобождает Пользователя от обязательств, возникших ранее.
6.3. Администрация оставляет за собой право отказывать в обслуживании Пользователям, чьи действия могут создавать повышенные риски для Сервиса, платёжных провайдеров или третьих лиц.
### 7. Платежи и возвраты
7.1. Оплата услуг и цифровых товаров производится на условиях, указанных в Сервисе до момента оплаты.
7.2. В связи с нематериальным характером цифровых товаров и услуг, **возврат денежных средств после предоставления доступа не осуществляется** , за исключением случаев, указанных ниже.
7.3. Возврат средств возможен только если:
* услуга не была оказана по технической вине Сервиса;
* доступ к цифровому товару фактически не был предоставлен.
7.4. Для рассмотрения вопроса о возврате Пользователь обязан обратиться в службу поддержки в течение **24 часов** с момента оплаты.
7.5. Решение о возврате принимается Администрацией индивидуально.
7.6. Пользователь подтверждает, что обязуется **не инициировать возврат платежа (chargeback)** через платёжные системы без предварительного обращения в службу поддержки Сервиса.
### 8. Конфиденциальность
8.1. Администрация может собирать минимально необходимые технические данные для обеспечения работы Сервиса.
8.2. Администрация принимает разумные меры для защиты данных, однако не гарантирует абсолютную безопасность передаваемой информации.
### 9. Изменение условий
9.1. Администрация вправе вносить изменения в настоящее Соглашение.
9.2. Актуальная версия Соглашения публикуется в Сервисе.
9.3. Продолжение использования Сервиса означает согласие Пользователя с обновлёнными условиями.
### 10. Контактная информация
10.1. По всем вопросам Пользователь может обратиться в службу поддержки через форму в самом боте.
**Используя Сервис (в том числе запуская бота и/или вводя команду /start), Пользователь подтверждает, что ознакомлен с настоящим Соглашением и принимает его условия в полном объёме.**
+64
View File
@@ -0,0 +1,64 @@
# Политика конфиденциальности
Политика конфиденциальности регулирует сбор, использование и защиту информации пользователей сервиса. Собираются идентификаторы аккаунта, техническая информация и история взаимодействий. Данные используются для обеспечения работы сервиса, связи с пользователем и анализа. Передача информации третьим лицам возможна только в законодательно установленных случаях или с согласия пользователя. Хранение данных осуществляется в течение необходимого срока, их защита — в разумных пределах. Пользователь самостоятельно несёт ответственность за риски, связанные с передачей данных. Администрация вправе вносить изменения в Политику без уведомления — согласие считается принятым при дальнейшем использовании сервиса.
**1. Общие положения**
1.1. Настоящая Политика конфиденциальности (далее — «Политика») регулирует порядок обработки и защиты информации, которую Пользователь передаёт при использовании сервиса (далее — «Сервис»).
1.2. Используя Сервис, Пользователь подтверждает своё согласие с условиями Политики. Если Пользователь не согласен с условиями — он обязан прекратить использование Сервиса.
---
**2. Сбор информации**
2.1. Сервис может собирать следующие типы данных:
* идентификаторы аккаунта (логин, ID, никнейм и т.п.);
* техническую информацию (IP-адрес, данные о браузере, устройстве и операционной системе);
* историю взаимодействий с Сервисом.
* 2.2. Сервис не требует от Пользователя предоставления паспортных данных, документов, фотографий или другой личной информации, кроме минимально необходимой для работы.
---
**3. Использование информации**
3.1. Сервис может использовать полученную информацию исключительно для:
* обеспечения работы функционала;
* связи с Пользователем (в том числе для уведомлений и поддержки);
* анализа и улучшения работы Сервиса.
---
**4. Передача информации третьим лицам**
4.1. Администрация не передаёт полученные данные третьим лицам, за исключением случаев:
* если это требуется по закону;
* если это необходимо для исполнения обязательств перед Пользователем (например, при работе с платёжными системами);
* если Пользователь сам дал на это согласие.
---
**5. Хранение и защита данных**
5.1. Данные хранятся в течение срока, необходимого для достижения целей обработки.
5.2. Администрация принимает разумные меры для защиты данных, но не гарантирует абсолютную безопасность информации при передаче через интернет.
---
**6. Отказ от ответственности**
6.1. Пользователь понимает и соглашается, что передача информации через интернет всегда сопряжена с рисками.
6.2. Администрация не несёт ответственности за утрату, кражу или раскрытие данных, если это произошло по вине третьих лиц или самого Пользователя.
---
**7. Изменения в Политике**
7.1. Администрация вправе изменять условия Политики без предварительного уведомления.
7.2. Продолжение использования Сервиса после внесения изменений означает согласие Пользователя с новой редакцией Политики.
+7 -16
View File
@@ -1,25 +1,16 @@
<template>
<div class="bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-white font-sans antialiased min-h-screen">
<div class="bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-white font-sans antialiased min-h-screen flex flex-col">
<AppHeader />
<HeroSection />
<FeaturesSection />
<CompatibilitySection />
<PricingSection />
<StepsSection />
<TestimonialsSection />
<FaqSection />
<main class="grow">
<RouterView />
</main>
<AppFooter />
</div>
</template>
<script setup>
import AppHeader from './components/AppHeader.vue'
import HeroSection from './components/HeroSection.vue'
import FeaturesSection from './components/FeaturesSection.vue'
import CompatibilitySection from './components/CompatibilitySection.vue'
import PricingSection from './components/PricingSection.vue'
import StepsSection from './components/StepsSection.vue'
import TestimonialsSection from './components/TestimonialsSection.vue'
import FaqSection from './components/FaqSection.vue'
import AppFooter from './components/AppFooter.vue'
</script>
</script>
+2 -1
View File
@@ -1 +1,2 @@
@import "tailwindcss";
@import "tailwindcss";
@plugin "@tailwindcss/typography";
+3 -3
View File
@@ -6,9 +6,9 @@
<span class="text-lg font-bold">uVPN</span>
</div>
<div class="flex gap-6 text-gray-500 dark:text-gray-400 text-sm">
<a href="#" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Политика конфиденциальности</a>
<a href="#" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Условия использования</a>
<a href="#" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Поддержка</a>
<router-link to="/docs/policy" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition hover:cursor-pointer">Политика конфиденциальности</router-link>
<router-link to="/docs/agreement" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 hover:cursor-pointer">Условия использования</router-link>
<a href="mailto:support@uvpn.shop" target="_blank" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition">Поддержка</a>
</div>
<p class="text-gray-500 dark:text-gray-400 text-sm">
© 2026 uVPN. Все права защищены.
+16 -18
View File
@@ -1,31 +1,29 @@
<template>
<header class="flex items-center justify-between px-6 py-4 md:px-12 border-b border-gray-200 dark:border-gray-800 sticky top-0 bg-white/80 dark:bg-gray-900/80 backdrop-blur-xs z-50">
<div class="flex items-center gap-3">
<!-- Логотип -->
<router-link to="/" class="flex items-center gap-3 hover:opacity-90 transition">
<img src="/images/logo.png" alt="uVPN logo" class="size-8" />
<span class="text-xl font-bold tracking-tight">uVPN</span>
</div>
</router-link>
<!-- Якорные ссылки -->
<!-- Навигация: теперь это router-link с хэшем секции -->
<nav class="hidden md:flex items-center gap-6 text-sm text-gray-500 dark:text-gray-400">
<a href="#hero" @click.prevent="scrollTo('hero')" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Главная</a>
<a href="#features" @click.prevent="scrollTo('features')" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Преимущества</a>
<a href="#pricing" @click.prevent="scrollTo('pricing')" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Тарифы</a>
<a href="#steps" @click.prevent="scrollTo('steps')" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Как начать</a>
<a href="#testimonials" @click.prevent="scrollTo('testimonials')" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">Отзывы</a>
<a href="#footer" @click.prevent="scrollTo('faq')" class="hover:text-gray-900 dark:hover:text-gray-400 dark:text-white transition">FAQ</a>
<router-link :to="{ path: '/', hash: '#hero' }" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition">Главная</router-link>
<router-link :to="{ path: '/', hash: '#features' }" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition">Преимущества</router-link>
<router-link :to="{ path: '/', hash: '#pricing' }" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition">Тарифы</router-link>
<router-link :to="{ path: '/', hash: '#steps' }" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition">Как начать</router-link>
<router-link :to="{ path: '/', hash: '#testimonials' }" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition">Отзывы</router-link>
<router-link :to="{ path: '/', hash: '#faq' }" class="hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400 transition">FAQ</router-link>
</nav>
<button class="bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 transition px-5 py-2 rounded-lg font-medium text-sm text-white">
<!-- Кнопка тарифов -->
<router-link :to="{ path: '/', hash: '#pricing' }" class="bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 transition px-5 py-2 rounded-lg font-medium text-sm text-white">
Попробовать бесплатно
</button>
</router-link>
</header>
</template>
<script setup>
const scrollTo = (id) => {
const el = document.getElementById(id)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
}
</script>
// Скрипты больше не нужны, всю магию сделает роутер!
</script>
+85
View File
@@ -0,0 +1,85 @@
<template>
<div class="max-w-4xl mx-auto px-4 py-12">
<!-- Навигация: теперь используем router-link вместо обычных кнопок -->
<nav class="flex space-x-6 mb-8 pb-2">
<router-link
to="/docs/policy"
class="px-1 py-2 text-sm font-medium transition-colors border-b-2 -mb-2.5"
active-class="border-blue-600 text-blue-600 dark:text-blue-400 dark:border-blue-400"
inactive-class="border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400"
>
Политика конфиденциальности
</router-link>
<router-link
to="/docs/agreement"
class="px-1 py-2 text-sm font-medium transition-colors border-b-2 -mb-2.5"
active-class="border-blue-600 text-blue-600 dark:text-blue-400 dark:border-blue-400"
inactive-class="border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400"
>
Пользовательское соглашение
</router-link>
</nav>
<!-- Область отображения Markdown (Tailwind Typography) -->
<div
v-if="htmlContent"
v-html="htmlContent"
class="prose prose-slate max-w-none dark:prose-invert"
></div>
<div v-else-if="isLoading" class="text-center py-12 text-slate-500 dark:text-slate-400">
Загрузка документа...
</div>
<div v-else class="text-center py-12 text-red-500">
Документ не найден или произошла ошибка.
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { marked } from 'marked'
// Принимаем параметр docName из роутера
const props = defineProps({
docName: {
type: String,
required: true
}
})
const htmlContent = ref('')
const isLoading = ref(true)
// Функция загрузки md-файла
const fetchMarkdown = async (name) => {
isLoading.value = true
htmlContent.value = ''
try {
// Загрузка из папки public/docs/
const response = await fetch(`/docs/${name}.md`)
if (!response.ok) throw new Error('Файл отсутствует')
const markdownText = await response.text()
htmlContent.value = marked(markdownText)
} catch (error) {
console.error('Ошибка отрисовки md:', error)
htmlContent.value = ''
} finally {
isLoading.value = false
}
}
// Загружаем файл при первой инициализации страницы
onMounted(() => {
fetchMarkdown(props.docName)
})
// Отслеживаем изменение ссылки (например, если пользователь перешел с policy на agreement)
watch(() => props.docName, (newName) => {
fetchMarkdown(newName)
})
</script>
+1 -1
View File
@@ -38,7 +38,7 @@
<div class="bg-white dark:bg-gray-900 rounded-2xl p-8 text-center hover:bg-gray-100 dark:hover:bg-gray-800 transition border border-gray-200 dark:border-gray-800">
<RefreshCw class="w-12 h-12 text-blue-600 dark:text-blue-400 mx-auto mb-4" />
<h3 class="text-xl font-semibold mb-3">Стабильная работа</h3>
<p class="text-gray-500 dark:text-gray-400">Автоматические обновления конфигураций VPN работает даже при новых блокировках</p>
<p class="text-gray-500 dark:text-gray-400">Автоматические обновления конфигураций мы первые подстраиваемся под новые условия</p>
</div>
</div>
</section>
+6 -1
View File
@@ -1,5 +1,10 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles.css'
createApp(App).mount('#app')
const app = createApp(App)
app.use(router)
app.mount('#app')
+34
View File
@@ -0,0 +1,34 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import DocViewer from '../components/DocViewer.vue'
const router = createRouter({
history: createWebHistory(),
scrollBehavior(to, from, savedPosition) {
// Если в ссылке есть хэш (например, #pricing)
if (to.hash) {
return new Promise((resolve) => {
// Даем 200 миллисекунд компонентам (Three.js/Swiper) на полную отрисовку
setTimeout(() => {
resolve({
el: to.hash,
behavior: 'smooth',
// Корректировка отступа сверху, чтобы липкая (sticky) шапка не перекрывала заголовок блока
offset: { x: 0, y: 80 }
})
}, 200)
})
}
// Если хэша нет, при переходе на новую страницу просто скроллим в самый верх
return { top: 0 }
},
routes: [
{ path: '/', name: 'home', component: HomeView },
{ path: '/docs/:docName', name: 'documents', component: DocViewer, props: true }
]
})
export default router
+21
View File
@@ -0,0 +1,21 @@
<template>
<div>
<HeroSection />
<FeaturesSection />
<CompatibilitySection />
<PricingSection />
<StepsSection />
<TestimonialsSection />
<FaqSection />
</div>
</template>
<script setup>
import HeroSection from '../components/HeroSection.vue'
import FeaturesSection from '../components/FeaturesSection.vue'
import CompatibilitySection from '../components/CompatibilitySection.vue'
import PricingSection from '../components/PricingSection.vue'
import StepsSection from '../components/StepsSection.vue'
import TestimonialsSection from '../components/TestimonialsSection.vue'
import FaqSection from '../components/FaqSection.vue'
</script>
-2
View File
@@ -2,7 +2,6 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
@@ -10,7 +9,6 @@ import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
tailwindcss(),
],
resolve: {