update landing
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-08-20 13:19:32 +03:00
parent 4771b374ac
commit c84b9788d9
15 changed files with 238 additions and 309 deletions

View File

@@ -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>

View File

@@ -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">

View File

@@ -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)

View File

@@ -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>&nbsp;
<span class="text-bold text-red">0</span>&nbsp;
<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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>