This commit is contained in:
BIN
i18n-2.xlsm
BIN
i18n-2.xlsm
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB |
BIN
public/img/samolet.jpg
Normal file
BIN
public/img/samolet.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 MiB |
@@ -7,25 +7,35 @@
|
||||
<q-list separator>
|
||||
<q-expansion-item
|
||||
group="FAQgroup"
|
||||
v-for="item in 5"
|
||||
v-for="item in 8"
|
||||
:key="item"
|
||||
switch-toggle-side
|
||||
header-class="text-h5"
|
||||
header-class="text-h6"
|
||||
expand-icon="mdi-plus"
|
||||
expanded-icon="mdi-close"
|
||||
expand-icon-class="text-brand2"
|
||||
class="fix-icon-color"
|
||||
>
|
||||
<template #header="{ expanded }">
|
||||
<div :class="expanded ? 'text-brand' : ''" class="w100">
|
||||
<div :class="expanded ? 'text-primary' : ''" class="w100">
|
||||
{{ $t('faq__question_' + item) }}
|
||||
</div>
|
||||
</template>
|
||||
<q-card>
|
||||
<q-card-section class="text-h6">
|
||||
<q-card-section class="text-grey-8">
|
||||
{{ $t('faq__answer_' + item) }}
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
</q-list>
|
||||
<div class="text-h6 text-primary q-py-md" align="center">
|
||||
{{ $t('faq__description') }}
|
||||
<a
|
||||
href="mailto:a-mart@ya.ru"
|
||||
style="text-decoration: none; color: inherit"
|
||||
>
|
||||
a-mart@ya.ru
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</slide-template>
|
||||
</template>
|
||||
@@ -35,11 +45,10 @@
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-expansion.q-expansion-item--collapsed :deep(.q-item__section--side .q-icon) {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.custom-expansion.q-expansion-item--expanded :deep(.q-item__section--side .q-icon) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
<style scoped lang="scss">
|
||||
.q-expansion-item--expanded.fix-icon-color:deep(i) {
|
||||
color: $primary;
|
||||
transition: transform 0.3s ease;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="w100 flex justify-between bg-grey-11 text-caption q-pa-md q-gutter-md">
|
||||
<div class="w100 flex justify-between bg-grey-11 text-caption q-pa-md q-gutter-y-md">
|
||||
<div class="flex column col-12 col-sm">
|
||||
<base-logo class="text-body1 q-pb-sm"/>
|
||||
<div class="flex items-center">
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex w100 q-pa-lg text-white relative-position"
|
||||
:class="!isAlignTop ? 'justify-center' : 'justify-around vert-height'"
|
||||
class="flex w100 text-white relative-position vert-height"
|
||||
:class="isAlignTop ? 'justify-center' : 'justify-end'"
|
||||
style="background-image: url('/img/samolet.jpg'); background-size: cover; height: 100vh;"
|
||||
>
|
||||
<svg v-if ="isAlignTop" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<polygon fill="white" points="0,0 55,0 45,101 0,101"/>
|
||||
</svg>
|
||||
<mesh-background v-if ="isAlignTop" style="z-index: -2"/>
|
||||
<div
|
||||
ref="slogan"
|
||||
class="flex items-center"
|
||||
>
|
||||
<div
|
||||
class="flex column justify-center q-pa-lg q-ma-lg q-gutter-y-md no-wrap bg-transperant"
|
||||
style="max-width: 400px; "
|
||||
class="flex bg-white column justify-center q-pa-lg q-ma-lg q-gutter-y-md no-wrap bg-transperant"
|
||||
style="max-width: 400px; border-radius: 12px; opacity: 0.8"
|
||||
:style="!isAlignTop ? 'text-align : center' : ''"
|
||||
>
|
||||
<div class="text-h5 text-grey">
|
||||
@@ -41,25 +38,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="image"
|
||||
class="text-red flex"
|
||||
>
|
||||
<img
|
||||
src="/img/1.png"
|
||||
class="q-ma-lg"
|
||||
style="object-fit: scale-down;"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<q-resize-observer @resize="checkAlign"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import meshBackground from 'components/meshBackground.vue'
|
||||
|
||||
const slogan = ref(null)
|
||||
const image = ref(null)
|
||||
|
||||
@@ -1,76 +1,103 @@
|
||||
<template>
|
||||
<slide-template title="price__title">
|
||||
<q-card
|
||||
class="flex column justify-center q-my-md items-center q-gutter-y-md"
|
||||
style="max-width: 400px"
|
||||
>
|
||||
<div class="flex column items-center">
|
||||
<div class="flex text-h6">
|
||||
<telegram-star color="gold" size="24px"/>
|
||||
<span class="q-ml-sm" style="text-decoration: line-through;">2</span>
|
||||
<span class="text-bold text-red">0</span>
|
||||
<span>- {{ $t('price__chat_per_day') }}</span>
|
||||
<div ref="container" class="w100 flex no-wrap">
|
||||
<q-resize-observer @resize="updateWidth" />
|
||||
<div class="w100 flex justify-center" v-if="containerWidth > 800">
|
||||
<q-card
|
||||
v-for="(item, idx) in tariff"
|
||||
:key="idx"
|
||||
class="q-pa-md q-ma-sm flex items-center column"
|
||||
style="width: 20%"
|
||||
>
|
||||
<price-section-item
|
||||
:name="item.name"
|
||||
:chats-qty="item.chatsQty"
|
||||
:price="item.price"
|
||||
/>
|
||||
</q-card>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<q-badge color="red" class="q-mr-sm">100% OFF</q-badge>
|
||||
<span>{{ $t('price__sale_date') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-card
|
||||
flat
|
||||
class="bg-grey-3"
|
||||
style="border-radius: 12px;"
|
||||
>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<telegram-star color="gold" size="48px"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label class="text-grey">
|
||||
{{ $t('price__stars_pay') }}
|
||||
</q-item-label>
|
||||
<q-item-label class="text-h6">
|
||||
Telegram Stars
|
||||
</q-item-label>
|
||||
<q-item-label class="text-grey">
|
||||
{{ $t('price__stars_description') }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-card>
|
||||
|
||||
<q-list class="q-my-none q-pa-md">
|
||||
<q-item
|
||||
v-for="item in priceItems"
|
||||
:key="item.id"
|
||||
dense
|
||||
<q-carousel
|
||||
v-else
|
||||
v-model="slide"
|
||||
transition-prev="scale"
|
||||
transition-next="scale"
|
||||
swipeable
|
||||
animated
|
||||
navigation
|
||||
padding
|
||||
height="225px"
|
||||
control-color="primary"
|
||||
class="w100"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar text-color="brand2">
|
||||
<q-icon v-if="item.icon" :name="item.icon" size="md"/>
|
||||
<span v-else class="text-bold">{{ item.text }}</span>
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ $t(item.label)}}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-carousel-slide
|
||||
v-for="(item, idx) in tariff"
|
||||
:key="idx"
|
||||
:name="item.name"
|
||||
class="flex justify-center q-pt-lmdq-pa-none q-ma-none"
|
||||
>
|
||||
<price-section-item
|
||||
:name="item.name"
|
||||
:chats-qty="item.chatsQty"
|
||||
:price="item.price"
|
||||
/>
|
||||
</q-carousel-slide>
|
||||
</q-carousel>
|
||||
</div>
|
||||
|
||||
<div class="q-ma-md" align="center" style="max-width: 60%;">
|
||||
{{ $t('price__tariff_description') }}
|
||||
</div>
|
||||
|
||||
<q-card
|
||||
flat
|
||||
class="bg-grey-3"
|
||||
style="border-radius: 12px;"
|
||||
>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<telegram-star color="gold" size="48px"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label class="text-grey">
|
||||
{{ $t('price__stars_pay') }}
|
||||
</q-item-label>
|
||||
<q-item-label class="text-h6">
|
||||
Telegram Stars
|
||||
</q-item-label>
|
||||
<q-item-label class="text-grey">
|
||||
{{ $t('price__stars_description') }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-card>
|
||||
</slide-template>
|
||||
|
||||
</slide-template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import telegramStar from 'components/TelegramStar.vue'
|
||||
import { ref } from 'vue'
|
||||
import SlideTemplate from 'components/SlideTemplate.vue'
|
||||
import PriceSectionItem from 'components/PriceSectionItem.vue'
|
||||
import telegramStar from 'components/TelegramStar.vue'
|
||||
|
||||
const priceItems = [
|
||||
{ id: 1, icon: 'mdi-all-inclusive', label: 'price_unlimited_users' },
|
||||
{ id: 2, icon: 'mdi-all-inclusive', label: 'price_unlimited_projects' },
|
||||
{ id: 3, text: '5', label: 'price_free_chats' },
|
||||
{ id: 4, icon: 'mdi-lifebuoy', label: 'price_support' }
|
||||
const tariff = [
|
||||
{ id: 1, name: 'TEST', price: null, chatsQty: 5 },
|
||||
{ id: 2, name: 'START', price: 1000, chatsQty: 15 },
|
||||
{ id: 3, name: 'PRO', price: 5000, chatsQty: 40 },
|
||||
{ id: 4, name: 'VIP', price: 12000, chatsQty: null }
|
||||
]
|
||||
|
||||
const containerWidth = ref(0)
|
||||
const cardWidth=ref(175)
|
||||
|
||||
const updateWidth = ({ width }) => {
|
||||
containerWidth.value = width
|
||||
cardWidth.value = width < 1200 ? 250 : 175
|
||||
}
|
||||
|
||||
const slide = ref(tariff[0].name)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
42
src/components/PriceSectionItem.vue
Normal file
42
src/components/PriceSectionItem.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="flex column items-center">
|
||||
<div class="text-h5 text-primary text-bold">
|
||||
{{ name }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center" style="min-height: 50px;">
|
||||
<div v-if="price" class="flex column items-center">
|
||||
<div class="flex no-wrap items-center">
|
||||
<telegram-star color="gold" size="24px" class="q-mr-xs"/>
|
||||
<span class="text-h4">{{ price }}</span>
|
||||
</div>
|
||||
<span class="text-caption">{{ $t('price__per_month') }}</span>
|
||||
</div>
|
||||
<div v-else class="text-bold text-h5">
|
||||
{{ $t('price__free_tax') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center q-pt-md" style="min-height: 50px;">
|
||||
<div class="flex no-wrap items-center">
|
||||
<span v-if="chatsQty" class="text-brand2 text-bold text-h5">
|
||||
{{ chatsQty }}
|
||||
</span>
|
||||
<q-icon v-else name="mdi-all-inclusive" size="md" color="brand2"/>
|
||||
<span class="q-pl-sm">
|
||||
{{ $t('price__chats')}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import telegramStar from 'components/TelegramStar.vue'
|
||||
|
||||
defineProps({
|
||||
name: String,
|
||||
price: Number,
|
||||
chatsQty: Number
|
||||
})
|
||||
</script>
|
||||
@@ -1,87 +1,77 @@
|
||||
<template>
|
||||
<slide-template title="problem__title">
|
||||
<div ref="container" class="w100">
|
||||
<slide-template>
|
||||
<div ref="container" class="w100 flex no-wrap q-pt-lg">
|
||||
<q-resize-observer @resize="updateWidth" />
|
||||
<div
|
||||
v-for="(row, rowIndex) in rows"
|
||||
:key="rowIndex"
|
||||
class="row q-py-none"
|
||||
:class="rowClass(row.length)"
|
||||
>
|
||||
<div
|
||||
v-for="(item, itemIndex) in row"
|
||||
:key="itemIndex"
|
||||
class="flex-item"
|
||||
:style="itemStyle(row.length)"
|
||||
<div class="w100 flex justify-center" v-if="containerWidth > 800">
|
||||
<q-card
|
||||
v-for="(item, idx) in problems"
|
||||
:key="idx"
|
||||
class="q-pa-md q-ma-sm"
|
||||
>
|
||||
<problem-section-item
|
||||
:icon="item.icon"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
style="overflow: hidden; min-width: 200px; max-width: 100%"
|
||||
style="overflow: hidden;"
|
||||
:style="{ width: cardWidth + 'px' }"
|
||||
/>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<q-carousel
|
||||
v-else
|
||||
v-model="slide"
|
||||
transition-prev="scale"
|
||||
transition-next="scale"
|
||||
swipeable
|
||||
animated
|
||||
navigation
|
||||
padding
|
||||
height="300px"
|
||||
control-color="primary"
|
||||
class="w100"
|
||||
>
|
||||
<q-carousel-slide
|
||||
v-for="(item, idx) in problems"
|
||||
:key="idx"
|
||||
:name="item.title"
|
||||
class="flex justify-center q-pa-none q-ma-none"
|
||||
>
|
||||
<problem-section-item
|
||||
:icon="item.icon"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
style="overflow: hidden; min-width: 100px; max-width: 200px;"
|
||||
/>
|
||||
</q-carousel-slide>
|
||||
</q-carousel>
|
||||
</div>
|
||||
</slide-template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import ProblemSectionItem from 'components/ProblemSectionItem.vue'
|
||||
import SlideTemplate from 'components/SlideTemplate.vue'
|
||||
|
||||
const problems = [
|
||||
{ id: 1, icon: 'mdi-account-group-outline', title: 'problem__address_book', description: 'problem__address_book_description' },
|
||||
{ id: 2, icon: 'mdi-clipboard-outline', title: 'problem__task_manager', description: 'problem__task_manager_description' },
|
||||
{ id: 2, icon: 'mdi-clipboard-outline', title: 'problem__tasks', description: 'problem__tasks_description' },
|
||||
{ id: 3, icon: 'mdi-calendar-month', title: 'problem__meeting', description: 'problem__meeting_description' },
|
||||
{ id: 4, icon: 'mdi-folder-open-outline', title: 'problem__files', description: 'problem__files_description' },
|
||||
{ id: 5, icon: 'mdi-lock-outline', title: 'problem__privacy', description: 'problem__privacy_description' }
|
||||
]
|
||||
|
||||
const baseWidth = 250
|
||||
const containerWidth = ref(0)
|
||||
const cardWidth = ref(175)
|
||||
|
||||
const updateWidth = ({ width }) => {
|
||||
containerWidth.value = width
|
||||
cardWidth.value = width < 1200 ? 250 : 175
|
||||
}
|
||||
|
||||
const maxPerRow = computed(() => {
|
||||
return Math.max(1, Math.floor(containerWidth.value / baseWidth))
|
||||
})
|
||||
|
||||
const rows = computed(() => {
|
||||
const total = problems.length
|
||||
const maxRow = maxPerRow.value
|
||||
|
||||
if (maxRow >= total) return [problems]
|
||||
|
||||
const rowCount = Math.ceil(total / maxRow)
|
||||
const baseItems = Math.floor(total / rowCount)
|
||||
const extra = total % rowCount
|
||||
const result = []
|
||||
|
||||
let start = 0
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const take = baseItems + (i < extra ? 1 : 0)
|
||||
result.push(problems.slice(start, start + take))
|
||||
start += take
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const rowClass = (count) => {
|
||||
return count === 1 ? 'justify-center' : 'justify-between'
|
||||
}
|
||||
|
||||
const itemStyle = (count) => {
|
||||
return {
|
||||
flex: `0 0 ${100 / count}%`,
|
||||
maxWidth: `${100 / count}%`
|
||||
}
|
||||
}
|
||||
const slide = ref(problems[0].title)
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div class="flex column items-center q-pa-md q-ma-md">
|
||||
<div class="flex column items-center">
|
||||
<div>
|
||||
<q-avatar
|
||||
color="brand"
|
||||
text-color="white"
|
||||
:icon
|
||||
font-size="65px"
|
||||
size="100px"
|
||||
font-size="45px"
|
||||
size="75px"
|
||||
class="q-my-md"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-bold text-h4">
|
||||
<div class="text-bold text-h5" align="center">
|
||||
{{ $t(title) }}
|
||||
</div>
|
||||
<div class="text-h6 text-grey-8" style="max-width: 250px; text-align: center;">
|
||||
<div class="text-grey-8 q-pt-sm" style="text-align: center;">
|
||||
{{ $t(description) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div class="flex column no-wrap">
|
||||
<div class="flex w100 justify-center text-h4 q-mt-md text-bold q-pa-md text-grey">
|
||||
<div class="flex column no-wrap q-mt-lg">
|
||||
<div
|
||||
v-if="title"
|
||||
class="flex w100 justify-center text-h4 q-mt-md text-bold q-pa-md text-grey"
|
||||
align="center"
|
||||
>
|
||||
{{ $t(title) }}
|
||||
</div>
|
||||
<div class="flex w100 justify-center q-pb-md column items-center">
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
id="background-canvas-wrapper"
|
||||
class="flex fit column bg-brand"
|
||||
style="opacity:0.65"
|
||||
>
|
||||
<canvas id="canvas" class="fit"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
onMounted(() => {
|
||||
const canvasBody = document.getElementById("canvas")
|
||||
const drawArea = canvasBody.getContext("2d")
|
||||
|
||||
const opts = {
|
||||
particleColor: "rgb(255,255,255)",
|
||||
lineColor: "rgb(200,200,200)",
|
||||
particleAmount: 50,
|
||||
defaultSpeed: 0.1,
|
||||
variantSpeed: 1,
|
||||
defaultRadius: 3,
|
||||
variantRadius: 2,
|
||||
linkRadius: 200
|
||||
}
|
||||
|
||||
const delay = 200
|
||||
let tid
|
||||
const rgb = opts.lineColor.match(/\d+/g)
|
||||
let w
|
||||
let h
|
||||
const particles = []
|
||||
|
||||
function resizeReset () {
|
||||
w = canvasBody.width = window.innerWidth
|
||||
h = canvasBody.height = window.innerHeight
|
||||
}
|
||||
|
||||
function deBouncer () {
|
||||
clearTimeout(tid)
|
||||
tid = setTimeout(function() {
|
||||
resizeReset()
|
||||
}, delay)
|
||||
}
|
||||
|
||||
function checkDistance (x1, y1, x2, y2) {
|
||||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
||||
}
|
||||
|
||||
function setup () {
|
||||
|
||||
resizeReset()
|
||||
for (let i = 0; i < opts.particleAmount; i++){
|
||||
particles.push(new Particle())
|
||||
}
|
||||
window.requestAnimationFrame(loop)
|
||||
}
|
||||
|
||||
function loop() {
|
||||
window.requestAnimationFrame(loop)
|
||||
drawArea.clearRect(0, 0, w, h)
|
||||
for (let i = 0; i < particles.length; i++){
|
||||
particles[i].update()
|
||||
particles[i].draw()
|
||||
}
|
||||
for (let i = 0; i < particles.length; i++){
|
||||
linkPoints(particles[i], particles)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function linkPoints (point1, hubs){
|
||||
for (let i = 0; i < hubs.length; i++) {
|
||||
const distance = checkDistance(point1.x, point1.y, hubs[i].x, hubs[i].y)
|
||||
const opacity = 1 - distance / opts.linkRadius
|
||||
if (opacity > 0) {
|
||||
drawArea.lineWidth = 0.5
|
||||
drawArea.strokeStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity})`
|
||||
drawArea.beginPath()
|
||||
drawArea.moveTo(point1.x, point1.y)
|
||||
drawArea.lineTo(hubs[i].x, hubs[i].y)
|
||||
drawArea.closePath()
|
||||
drawArea.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Particle () {
|
||||
this.x = Math.random() * w
|
||||
this.y = Math.random() * h
|
||||
this.speed = opts.defaultSpeed + Math.random() * opts.variantSpeed
|
||||
this.directionAngle = Math.floor(Math.random() * 360)
|
||||
this.color = opts.particleColor
|
||||
this.radius = opts.defaultRadius + Math.random() * opts. variantRadius
|
||||
this.vector = {
|
||||
x: Math.cos(this.directionAngle) * this.speed,
|
||||
y: Math.sin(this.directionAngle) * this.speed
|
||||
};
|
||||
this.update = function(){
|
||||
this.border();
|
||||
this.x += this.vector.x
|
||||
this.y += this.vector.y
|
||||
};
|
||||
this.border = function(){
|
||||
if (this.x >= w || this.x <= 0) {
|
||||
this.vector.x *= -1;
|
||||
}
|
||||
if (this.y >= h || this.y <= 0) {
|
||||
this.vector.y *= -1;
|
||||
}
|
||||
if (this.x > w) this.x = w
|
||||
if (this.y > h) this.y = h
|
||||
if (this.x < 0) this.x = 0
|
||||
if (this.y < 0) this.y = 0
|
||||
}
|
||||
this.draw = function(){
|
||||
drawArea.beginPath()
|
||||
drawArea.arc(this.x, this.y, this.radius, 0, Math.PI*2)
|
||||
drawArea.closePath()
|
||||
drawArea.fillStyle = this.color
|
||||
drawArea.fill()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", function(){ deBouncer() })
|
||||
resizeReset()
|
||||
setup()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#background-canvas-wrapper {
|
||||
position: absolute !important;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1 +1 @@
|
||||
export default { EN: 'EN', RU: 'RU', '': '', main__how_it_works: 'How it works?', main__price: 'Price', main__faq: 'FAQ', main__contacts: 'Contacts', banner__slogan_prepend: 'banner__slogan_prepend', banner__slogan_body: 'banner__slogan_body', banner__main_btn: 'Join!', problem__title: 'problem__title', problem__address_book: 'problem__address_book', problem__address_book_description: 'problem__address_book_description', problem__task_manager: 'problem__task_manager', problem__task_manager_description: 'problem__task_manager_description', problem__meeting: 'problem__meeting', problem__meeting_description: 'problem__meeting_description', problem__files: 'problem__files', problem__files_description: 'problem__files_description', problem__privacy: 'problem__privacy', problem__privacy_description: 'problem__privacy_description', how_it_works__title: 'how_it_works__title', how_works__step1: 'Step 1', how_works__step1_description: 'how_works__step1_description', how_works__step2: 'Step 2', how_works__step2_description: 'how_works__step2_description', how_works__step3: 'Step 3 (optional)', how_works__step3_description: 'how_works__step3_description', how_works__step4: 'Done!', how_works__step4_description: 'how_works__step3_description', how_works__step_user: 'User', how_works__step_admin: 'Administrator', price__title: 'Price', price__chat_per_day: 'price__chat_per_day', price__sale_date: 'price__sale_date', price__stars_pay: 'price__stars_pay', price__stars_description: 'price__stars_description', price_unlimited_users: 'Unlimited users', price_unlimited_projects: 'Unlimited projects', price_free_chats: 'price_free_chats', price_support: 'Support', FAQ__title: 'FAQ', faq__question_1: 'faq__question_1', faq__answer_1: 'faq__answer_1', faq__question_2: 'faq__question_2', faq__answer_2: 'faq__answer_2', faq__question_3: 'faq__question_3', faq__answer_3: 'faq__answer_3', faq__question_4: 'faq__question_4', faq__answer_4: 'faq__answer_4', faq__question_5: 'faq__question_5', faq__answer_5: 'faq__answer_5', footer__docs: 'App documents', footer__doc_terms_of_use: 'Term of use', footer__doc_privacy_policy: 'Privacy Policy', footer__contacts_location: 'Russia, Moscow/Voronezh', footer__description_user_data: 'The site does not collect user data, use cookies, or track user activity.' }
|
||||
export default { EN: 'EN', RU: 'RU', '': '', main__how_it_works: 'How it works?', main__price: 'Tariff', main__faq: 'FAQ', main__contacts: 'Contacts', banner__slogan_prepend: 'More than just chats', banner__slogan_body: 'Your project\'s workspace in', banner__main_btn: 'Fly', problem__address_book: 'Address Book', problem__address_book_description: 'No more guessing who\'s who.', problem__tasks: 'Tasks', problem__tasks_description: 'Did you agree on something? Lock it in!', problem__meeting: 'Meeting', problem__meeting_description: 'A meeting or a conference call — schedule it right in the chat.', problem__files: 'Files', problem__files_description: 'No need to remember which chat had that file — everything is in one place now.', problem__privacy: 'Access Rights', problem__privacy_description: 'Users can only see information from the chats they are in.', how_it_works__title: 'How it works?', how_works__step1: 'Step 1', how_works__step1_description: 'Create a new project or select an existing one', how_works__step2: 'Step 2', how_works__step2_description: 'Attach a chat to the app', how_works__step3: 'Step 3 (Optional)', how_works__step3_description: 'Set up the address book', how_works__step4: 'Done!', how_works__step4_description: 'Access via the button in the pinned message', how_works__step_user: 'User', how_works__step_admin: 'Administrator', price__title: 'Tariff', price__per_month: 'per month', price__free_tax: 'FREE', price__chats: 'chats', price__stars_pay: 'Payment via', price__stars_description: 'Telegram\'s internal currency', price__tariff_description: 'All plans include full functionality: an unlimited number of users, projects, tasks, and meetings.', FAQ__title: 'Frequently Asked Questions (FAQ)', faq__question_1: 'Who is this app for?', faq__answer_1: 'If you actively communicate on projects in Telegram chats, this app will create a unified information space for you, where you can also connect contractors.', faq__question_2: 'Can I buy a subscription directly, without using Telegram Stars?', faq__answer_2: 'Unfortunately, that\'s not possible. We use Telegram Stars as the only payment system for mini-apps—this is a rule of the platform itself.', faq__question_3: 'Why does the bot need chat admin rights?', faq__answer_3: 'To help you! For the core functions to work—collecting files, creating meeting and task notifications right in the chat—the bot needs rights to read messages and manage messages. This is standard practice for functional bots. We guarantee that your data is confidential and is not shared with third parties (learn more in our Privacy Policy).', faq__question_4: 'Where are my files and data stored?', faq__answer_4: 'Your files and messages remain in your Telegram chats on Telegram\'s servers. We do not store them ourselves. The app merely organizes them, providing convenient search and display.', faq__question_5: 'Why are some contacts in the address book not fully displayed?', faq__answer_5: 'The contact information (name, position, phone, email) is entered into the system by your project\'s administrator. However, for this full data to be displayed to other participants, the user themselves must give their consent upon first launching the app. This is a requirement of personal data protection laws. Thus, you will only see the information that the administrator has added to your book and that the user has permitted to be shown.', faq__question_6: 'What happens if I remove the bot from a chat or stop tracking?', faq__answer_6: 'The app will stop receiving new messages and files from that chat. All previously collected information will remain in your history and will be available for search, but it will no longer update. This is convenient when a project is completed but its archive needs to be preserved.', faq__question_7: 'Can one chat be linked to several projects at once?', faq__answer_7: 'No, one chat can only be linked to one project. This helps maintain order and avoids confusing participants. But you can easily switch between all projects you have access to—even from different administrators.', faq__question_8: 'Can participants of one project see data from another?', faq__answer_8: 'No, all projects are completely independent. This is done for your confidentiality. Information from one project (chats, files, tasks, contacts) never leaks into another. Moreover, the same user can appear in different projects in different roles and from different companies. When switching between projects, you only see the information relevant to the selected project.', faq__description: 'Still have questions? We\'re here to help:', footer__docs: 'App documents', footer__doc_terms_of_use: 'Term of use', footer__doc_privacy_policy: 'Privacy Policy', footer__contacts_location: 'Russia, Moscow/Voronezh', footer__description_user_data: 'The site does not collect user data, use cookies, or track user activity.' }
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,14 @@
|
||||
<template>
|
||||
<q-layout view="lHr lpr lFr" class="bg-transparent">
|
||||
<q-header class="main-content text-grey glass">
|
||||
<div ref="headerContainer" class="flex q-ma-md justify-between no-wrap items-center">
|
||||
<q-layout view="lHr lpr lFr" class="bg-transparent relative-position">
|
||||
<q-header
|
||||
class="main-content q-py-sm"
|
||||
:class="isHeroScroll ? 'text-white bg-transparent' : 'text-grey glass'"
|
||||
reveal
|
||||
>
|
||||
<div
|
||||
ref="headerContainer"
|
||||
class="flex q-mx-md justify-between no-wrap items-center"
|
||||
>
|
||||
<base-logo ref="logo" class="text-h6"/>
|
||||
<div
|
||||
ref="menuContainer"
|
||||
@@ -12,7 +19,7 @@
|
||||
ref="buttonsContainer"
|
||||
:class="{ 'invisible absolute': !showFullMenu }"
|
||||
class="flex row no-wrap"
|
||||
>
|
||||
>
|
||||
<q-btn
|
||||
v-for="item in menuItems"
|
||||
:key="item.id"
|
||||
@@ -20,7 +27,7 @@
|
||||
no-caps
|
||||
@click="scrollToElement(item.ref)"
|
||||
ref="menuButtons"
|
||||
>
|
||||
>
|
||||
<span class="text-no-wrap">{{ $t(item.title) }}</span>
|
||||
</q-btn>
|
||||
</div>
|
||||
@@ -31,7 +38,11 @@
|
||||
icon="menu"
|
||||
@click="showDrawer = !showDrawer"
|
||||
/>
|
||||
<q-btn outline color="primary" class="q-ml-sm">
|
||||
<q-btn
|
||||
outline
|
||||
:color="isHeroScroll ? 'white' : 'primary'"
|
||||
class="q-ml-sm"
|
||||
>
|
||||
<div class="flex items-center no-wrap">
|
||||
<span class="text-bold">{{ locale.split('-')[0] }}</span>
|
||||
</div>
|
||||
@@ -80,8 +91,9 @@
|
||||
<q-page-container
|
||||
class="main-content q-pa-none q-ma-none bg-transparent"
|
||||
>
|
||||
<q-scroll-observer axis="vertical" @scroll="updateHeaderStyle"/>
|
||||
<q-page class="column">
|
||||
<hero-banner/>
|
||||
<hero-banner class="q-pa-none" style="margin-top: -58px;" id='hero_banner'/>
|
||||
<problem-section/>
|
||||
<how-works-section id='how_works'/>
|
||||
<price-section id='price'/>
|
||||
@@ -103,6 +115,12 @@
|
||||
import FaqSection from 'components/FAQSection.vue'
|
||||
import FooterSection from 'components/FooterSection.vue'
|
||||
|
||||
const isHeroScroll = ref(true)
|
||||
|
||||
const updateHeaderStyle = (e) => {
|
||||
isHeroScroll.value = e.position.top <= 5
|
||||
}
|
||||
|
||||
const showDrawer = ref(false)
|
||||
|
||||
const menuItems = [
|
||||
|
||||
Reference in New Issue
Block a user