Compare commits

..

11 Commits

Author SHA1 Message Date
2fc4deb9e7 small fix
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-23 08:50:54 +03:00
5213e7b2e5 update
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-22 13:54:18 +03:00
03b2a8306d 2
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-19 00:17:05 +03:00
fdbfb679d8 1
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-19 00:11:55 +03:00
5633ae84ff glass fix
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-19 00:07:32 +03:00
cce5d98e75 f
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-19 00:03:21 +03:00
e985b94ca4 small-fix
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-18 23:59:35 +03:00
0e8cd99212 glass
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-18 23:26:05 +03:00
cd1a571636 small fix
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-18 23:15:05 +03:00
6f5a3cc6cc Delete ~$i18n-2-landing.xlsm
All checks were successful
continuous-integration/drone/push Build is passing
2026-04-18 23:01:13 +03:00
7deca1df27 simple_fix 2026-04-18 23:01:08 +03:00
17 changed files with 566 additions and 253 deletions

Binary file not shown.

View File

@@ -1,19 +1,17 @@
<template>
<div class="flex row items-center no-wrap logo-component">
<LogoIcon
v-bind="$attrs"
class="logo-svg"
:class="{
'is-animated': animated,
'hide-bg': !withBackground
}"
:style="{ color: iconColor }"
:style="{ color: iconColor, transform: 'scale(1.08)', height: '1cap' }"
/>
<div
:class="'text-' + textColor"
class="logo-text"
style="margin-left: 0.05em;"
style="margin-left: 0.15em;"
>
<span>tg</span>
<span class="text-bold">Crew</span>
@@ -23,7 +21,6 @@
<script setup>
import LogoIcon from 'components/BaseLogoSvg.vue'
defineProps({
textColor: {
type: String,
@@ -45,38 +42,4 @@
</script>
<style lang="scss" scoped>
.logo-svg {
height: 1em;
width: 1em;
:deep(.logo-bg) {
transition: opacity 0.3s;
}
&.hide-bg :deep(.logo-bg) {
display: none;
}
:deep(.x), :deep(.c) {
transform-box: fill-box;
}
&.is-animated {
:deep(.x) { animation: expand-r 6s ease-in-out infinite; }
:deep(.c) { animation: o 6s ease-in-out infinite; }
:deep(.l) {
stroke-dasharray: 1;
stroke-dashoffset: 1;
stroke-width: 1.5;
animation: draw 6s ease-in-out infinite;
}
}
}
@keyframes expand-r { 50% { r: 8px; } }
@keyframes o { 50% { transform: var(--t); } }
@keyframes draw {
0%, 100% { stroke-dashoffset: 0; }
50% { stroke-dashoffset: 1; }
}
</style>

View File

@@ -2,13 +2,22 @@
<svg
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
v-bind="$attrs"
:class="{ 'is-animated': animated }"
class="base-logo-svg"
>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="logo-gradient">
<stop stop-color="#2AABEE" offset="0%"></stop>
<stop stop-color="#229ED9" offset="100%"></stop>
</linearGradient>
<radialGradient id="logo-gradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" stop-color="white" />
<stop offset="25%" stop-color="var(--q-primary, #1976D2)" stop-opacity="0.5" />
<stop offset="50%" stop-color="var(--q-primary, #1976D2)">
<animate
attributeName="offset"
values="1;0.6;1"
dur="6s"
repeatCount="indefinite"
/>
</stop>
</radialGradient>
</defs>
<rect class="logo-bg" width="32" height="32" rx="4" ry="4" fill="url(#logo-gradient)" />
@@ -26,8 +35,35 @@
</svg>
</template>
<script setup lang="ts">
defineOptions({
inheritAttrs: false
})
<script setup>
defineProps({
animated: {
type: Boolean,
default: true
}
})
</script>
<style lang="scss" scoped>
.base-logo-svg {
.x, .c { transform-box: fill-box; }
.l { stroke-width: 1.5; }
&.is-animated {
.x { animation: expand-r 6s infinite; }
.c { animation: o 6s infinite; }
.l {
stroke-dasharray: 1;
stroke-dashoffset: 1;
animation: draw 6s infinite;
}
}
}
@keyframes expand-r { 50% { r: 8px; } }
@keyframes o { 50% { transform: var(--t); } }
@keyframes draw {
0%, 100% { stroke-dashoffset: 0; }
50% { stroke-dashoffset: 1; }
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<div class="w100">
<div class="row justify-center w100">
<q-resize-observer @resize="checkSpace" />
<div
class="tabs-wrapper no-pointer-events"
style="position: absolute !important; visibility: hidden; height: 0"
>
<q-tabs
ref="hiddenTabsRef"
dense
shrink
align="center"
class="text-grey custom-tabs"
active-color="primary"
indicator-color="primary"
narrow-indicator
:arrows="false"
:outside-arrows="false"
:mobile-arrows="false"
>
<q-tab
v-for="tabItem in tabItems"
:key="tabItem.name"
:name="tabItem.name"
:label="$t(tabItem.label)"
class="custom-tab-item"
/>
</q-tabs>
</div>
<div :class="['tabs-wrapper', fogClass]">
<q-tabs
v-model="tab"
ref="tabsRef"
dense
shrink
align="center"
class="text-grey custom-tabs"
active-color="primary"
indicator-color="primary"
narrow-indicator
:arrows="false"
:outside-arrows="false"
:mobile-arrows="false"
:no-caps="isOverflowing || useTabsNoCaps"
@update:model-value="onTabChange"
>
<q-tab
v-for="tabItem in tabItems"
:key="tabItem.name"
:name="tabItem.name"
:label="$t(tabItem.label)"
class="custom-tab-item"
/>
<q-resize-observer @resize="updateScrollInfo" />
</q-tabs>
</div>
</div>
<div class="column items-center w100">
<slot :item="activeItem"/>
</div>
</div>
</template>
<script setup>
import { ref, computed, nextTick, onMounted } from 'vue'
const props = defineProps({
tabItems: { type: Array, required: true },
useTabsNoCaps: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:tabs-no-caps'])
const tab = ref(props.tabItems[0]?.name)
const tabsRef = ref(null)
const hiddenTabsRef = ref(null)
const canScroll = ref(false)
const isOverflowing = ref(false)
const updateScrollInfo = () => {
const el = tabsRef.value?.$el.querySelector('.q-tabs__content')
if (el) canScroll.value = el.scrollWidth > el.clientWidth + 1
}
const checkSpace = (size) => {
const hiddenTabs = hiddenTabsRef.value?.$el.querySelector('.q-tabs__content')
if (hiddenTabs) {
isOverflowing.value = hiddenTabs.scrollWidth > size.width
if (isOverflowing.value !== props.useTabsNoCaps) {
emit('update:tabs-no-caps', isOverflowing.value)
}
}
}
const activeItem = computed(() =>
props.tabItems.find(t => t.name === tab.value) || props.tabItems[0]
)
const fogClass = computed(() => {
const activeNoCaps = isOverflowing.value || props.useTabsNoCaps
if (!canScroll.value || !activeNoCaps) return ''
const index = props.tabItems.findIndex(t => t.name === tab.value)
if (index === 0) return 'is-right'
if (index === props.tabItems.length - 1) return 'is-left'
return 'is-both'
})
const onTabChange = async() => {
await nextTick()
if (tabsRef.value) updateScrollInfo()
}
onMounted(async () => {
await nextTick()
updateScrollInfo()
})
</script>
<style scoped lang="scss">
.tabs-wrapper {
position: relative;
display: inline-block;
max-width: 100%;
&::before,
&::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 60px;
z-index: 10;
pointer-events: none;
transition: opacity 0.3s;
opacity: 0;
}
&::before {
left: 0;
background: linear-gradient(to right, $grey-11, transparent);
}
&::after {
right: 0;
background: linear-gradient(to left, $grey-11, transparent);
}
&.is-left::before,
&.is-both::before { opacity: 1; }
&.is-right::after,
&.is-both::after { opacity: 1; }
}
.custom-tabs {
background: transparent !important;
:deep(.q-tabs__content) {
overflow: hidden !important;
}
:deep(.q-tabs__arrow),
:deep(.q-icon) {
display: none !important;
}
:deep(.q-tab--no-caps) {
padding: 0 8px;
}
}
.custom-tab-item {
flex-shrink: 0;
white-space: nowrap;
}
</style>

View File

@@ -2,7 +2,7 @@
<div class="column bg-grey-4 rounded-card q-px-xl q-pt-md">
<div class="row no-wrap items-center q-pb-md">
<div class="text-h6" style="flex-grow: 2;">
{{ $t(question) }}
{{ $t(question ?? '') }}
</div>
<div class="q-pl-md">
<q-btn
@@ -20,7 +20,7 @@
<q-slide-transition>
<div v-if="showAnswer">
<div class="text-body text-grey-9 q-pb-md">
{{ $t(answer) }}
{{ $t(answer ?? '') }}
</div>
</div>
</q-slide-transition>
@@ -28,7 +28,7 @@
</div>
</template>
<script setup lang="ts">
<script setup>
import { ref } from 'vue'
defineProps({

View File

@@ -1,16 +1,21 @@
<template>
<div class="w100 flex justify-between text-caption q-pa-md q-gutter-y-md text-grey-9">
<div class="flex column col-12 col-sm">
<base-logo class="text-body1 q-pb-sm"/>
<div class="flex items-center">
<div class="q-pl-sm">
<base-logo
withBackground
animated
class="text-h4"
/>
<div class="flex items-center no-wrap">
<q-icon name="mdi-map-marker-outline" color="brand" class="q-pr-sm"/>
<span>{{ $t('footer__contacts_location') }}</span>
</div>
<div class="flex items-center">
<div class="flex items-center no-wrap">
<q-icon name="mdi-phone-outline" color="brand" class="q-pr-sm" />
<span>+7 (916) 439-04-25</span>
</div>
<div class="flex items-center">
<div class="flex items-center no-wrap">
<q-icon name="mdi-email-outline" color="brand" class="q-pr-sm"/>
<a
href="mailto:info@tgcrew.ru"
@@ -19,9 +24,30 @@
info@tgcrew.ru
</a>
</div>
<br>
<div class="">
{{ $t('footer__contacts_ogrnip') + ' ' }}
<span class="text-no-wrap">
318774600262084
</span>
</div>
<div class="">
{{ $t('footer__contacts_inn') + ' ' }}
<span class="text-no-wrap">
366316608346
</span>
</div>
<div class="">
{{ $t('footer__contacts_pdn') + ' ' }}
<span class="text-no-wrap">
77-25-471585
</span>
</div>
</div>
</div>
<div class="flex column col-12 col-sm">
<div class="q-pl-sm">
<span class="text-body1 text-bold">
{{ $t('footer__docs') }}
</span>
@@ -39,10 +65,10 @@
</span>
</div>
</div>
</div>
<div class="flex column col-12 col-sm">
<div class="text-grey">
<div class="text-grey q-pl-sm">
{{ $t('footer__description_user_data') }}
</div>
</div>

View File

@@ -29,7 +29,7 @@
class="column justify-end text-grey-4 text-center edge-block"
:class="$q.screen.lt.sm ? 'text-h5' : 'text-h4'"
>
{{ $t('banner__slogan_prepend') }} &mdash;
{{ $t('banner__slogan_prepend') }}
</div>
<div
@@ -62,7 +62,7 @@
<q-icon name="keyboard_arrow_right"/>
</div>
</q-btn>
<div class="text-white q-pt-md edge-block justify-start text-grey-2">
<div class="text-white q-pt-sm edge-block justify-start text-grey-2">
{{ $t('banner__main_btn_description')}}
</div>
</div>

View File

@@ -1,69 +1,56 @@
<template>
<slide-template
<slide-template
title="how_it_works__title"
class="overflow-hidden w100"
>
<q-tabs
v-model="tab"
dense
class="text-grey"
active-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
>
<q-tab
v-for="tab in tabs"
:key="tab.name"
:name="tab.name"
:label="$t(tab.label)"
/>
</q-tabs>
<q-tab-panels
v-model="tab"
animated
class="w60 bg-transparent"
>
<q-tab-panel
v-for="tab in tabs"
:key="tab.name"
:name="tab.name"
>
<div
class="relative-position w100">
<div
class="abolute-position w100"
style="height: 60vh;"
<custom-tabs
v-bind="$attrs"
:tabItems
:useTabsNoCaps
>
<template #default="{ item }">
<div class="relative-position w60 q-pt-md">
<div style="height: 60vh; position: relative;">
<iframe
src="https://kinescope.io/embed/5yesvjfi6XAYRuxzbsYGHZ"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media; gyroscope; accelerometer; clipboard-write; screen-wake-lock; fullscreen;" frameborder="0" style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
>
</iframe>
:key="item.name"
:src="item.url"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media; gyroscope; accelerometer; clipboard-write; screen-wake-lock; fullscreen;"
frameborder="0"
style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
/>
</div>
</div>
<!-- <q-skeleton
style="height: calc(100vw * 0.5 / 16 * 9)"
square
/> -->
</q-tab-panel>
</q-tab-panels>
</slide-template>
</template>
</custom-tabs>
</slide-template>
</template>
<script setup lang="ts">
import { ref } from 'vue'
<script setup>
import SlideTemplate from 'components/SlideTemplate.vue'
const tabs = [
{ name: 'intro', label: 'how_it_works__intro'},
{ name: 'admin', label: 'how_it_works__admin'},
{ name: 'user', label: 'how_it_works__user'}
import CustomTabs from 'components/CustomTabs.vue'
defineProps({
useTabsNoCaps: Boolean
})
const tabItems = [
{
name: 'intro',
label: 'how_it_works__intro',
url: 'https://kinescope.io/embed/5yesvjfi6XAYRuxzbsYGHZ'
},
{
name: 'admin',
label: 'how_it_works__admin',
url: 'https://kinescope.io/embed/5yesvjfi6XAYRuxzbsYGHZ'
},
{
name: 'user',
label: 'how_it_works__user',
url: 'https://kinescope.io/embed/5yesvjfi6XAYRuxzbsYGHZ'
}
]
const tab = ref(tabs[0].name)
</script>
<style scoped>
<style scoped lang="scss">
</style>

View File

@@ -3,34 +3,24 @@
title="price__title"
subtitle="price__subtitle"
>
<q-tabs
v-model="tab"
dense
class="text-grey"
active-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
<custom-tabs
v-bind="$attrs"
:tabItems
:useTabsNoCaps
>
<q-tab
v-for="tab in tabs"
:key="tab.name"
:name="tab.name"
:label="$t(tab.label)"
/>
</q-tabs>
<template #default="{ item }">
<div
class="fit row q-pb-lg"
>
<div class="col-md-3 col-sm-6 col-xs-12 q-pa-lg"
v-for="(item, idx) in tariff"
v-for="(tariff, idx) in tariffs"
:key="idx"
>
<price-section-item
:name="item.name"
:chats-qty="item.chatsQty"
:price="tab === 'legal' ? item.price_rub : item.price"
:price_unit="tab === 'legal' ? 'rub' : 'stars'"
:name="tariff.name"
:chats-qty="tariff.chatsQty ?? 0"
:price="item.name === 'legal' ? tariff.price_rub : tariff.price"
:price_unit="item.name === 'legal' ? 'rub' : 'stars'"
/>
</div>
</div>
@@ -38,7 +28,7 @@
flat
class="bg-white rounded-card"
>
<q-item v-if="tab === 'legal'">
<q-item v-if="item.name === 'legal'">
<q-item-section avatar>
<span class="text-h4 text-grey q-px-sm">
@@ -56,7 +46,7 @@
</q-item-label>
</q-item-section>
</q-item>
<q-item v-if="tab === 'individual'">
<q-item v-if="item.name === 'individual'">
<q-item-section avatar>
<telegram-star color="gold" size="48px"/>
</q-item-section>
@@ -73,22 +63,27 @@
</q-item-section>
</q-item>
</q-card>
</template>
</custom-tabs>
</slide-template>
</template>
<script setup lang="ts">
import { ref } from 'vue'
<script setup>
import SlideTemplate from 'components/SlideTemplate.vue'
import CustomTabs from 'components/CustomTabs.vue'
import PriceSectionItem from 'components/PriceSectionItem.vue'
import telegramStar from 'components/TelegramStar.vue'
import TelegramStar from 'components/TelegramStar.vue'
const tabs = [
defineProps({
useTabsNoCaps: Boolean
})
const tabItems = [
{ name: 'legal', label: 'price__legal'},
{ name: 'individual', label: 'price__individual'}
]
const tab = ref(tabs[0].name)
const tariff = [
const tariffs = [
{ id: 1, name: 'TEST', price: null, price_rub: null, chatsQty: 5 },
{ id: 2, name: 'START', price: 1000, price_rub: 2000, chatsQty: 15 },
{ id: 3, name: 'PRO', price: 5000, price_rub: 10000, chatsQty: 40 },
@@ -97,5 +92,5 @@
</script>
<style scoped>
<style scoped lang="scss">
</style>

View File

@@ -6,10 +6,10 @@
color="primary"
/>
<div class="text-bold text-h5 text-center q-pt-md">
{{ $t(title) }}
{{ $t(title ?? '') }}
</div>
<div class="text-grey text-body1 text-center q-pt-sm text-wrap">
{{ $t(description) }}
{{ $t(description ?? '') }}
</div>
</div>
</template>

View File

@@ -1,3 +1,40 @@
// app global css in SCSS form
.bg-t-primary {
background: $t-bg-color !important;
}
.bg-t-secondary {
background: $t-secondary-bg-color !important;
}
.bg-t-bottom-bar {
background: $t-bottom-bar-bg-color !important;
}
.t-section-separator {
color: $t-section-separator-color !important;
}
.t-text {
color: $t-text-color !important;
}
.t-text-hint {
color: $t-hint-color !important;
}
.t-text-subtitle {
color: $t-subtitle-text-color !important;
}
.t-text-accent {
color: $t-accent-text-color !important;
}
.t-text-section-header {
color: $t-section-header-text-color !important;
}
.text-brand {
color: $brand !important;
}
@@ -6,6 +43,18 @@
background: $brand !important;
}
.bg-base {
background: $base-bg !important;
}
.text-tgcolor {
color: $tgcolor !important;
}
.bg-tgcolor {
background: $tgcolor !important;
}
$base-width: 100;
@while $base-width > 0 {
.w#{$base-width} { width: #{$base-width}+'%'; }
@@ -14,9 +63,9 @@ $base-width: 100;
body, html, #q-app {
font-family: $typography-font-family;
background-color: transparent !important;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
* {
@@ -27,6 +76,26 @@ body, html, #q-app {
--body-width: 1240px;
--logo-color-bg-white: grey;
--base-radius: 24px;
--t-bg-color: #{$t-bg-color};
--t-secondary-bg-color: #{$t-secondary-bg-color};
--t-section-bg-color: #{$t-section-bg-color};
--t-header-bg-color: #{$t-header-bg-color};
--t-bottom-bar-bg-color: #{$t-bottom-bar-bg-color};
--t-text-color: #{$t-text-color};
--t-hint-color: #{$t-hint-color};
--t-subtitle-text-color: #{$t-subtitle-text-color};
--t-accent-text-color: #{$t-accent-text-color};
--t-section-header-text-color: #{$t-section-header-text-color};
--t-button-color: #{$t-button-color};
--t-button-text-color: #{$t-button-text-color};
--t-link-color: #{$t-link-color};
--t-destructive-text-color: #{$t-destructive-text-color};
--t-section-separator-color: #{$t-section-separator-color};
}
.main-content {

View File

@@ -1,18 +1,45 @@
// Quasar SCSS (& Sass) Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
/* telegram theme on Android 2026
bg-color: #ffffff
secondary-bg-color: #f1f1f3
section-bg-color: #ffffff
header-bg-color: #ffffff
bottom-bar-bg-color: #f1f1f3
// Check documentation for full list of Quasar variables
text-color: #1a1d21
hint-color: #a8a8a8
subtitle-text-color: #82868a
accent-text-color: #1c93e3
section-header-text-color: #298acf
// Your own variables (that are declared here) and Quasar's own
// ones will be available out of the box in your .vue/.scss/.sass files
// It's highly recommended to change the default colors
// to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website.
button-color: #229af0
button-text-color: #ffffff
link-color: #2678b6
destructive-text-color: #cc2929
$primary : #3390ec;
section-separator-color: #d9d9d9
*/
$t-bg-color: #ffffff;
$t-secondary-bg-color: #f1f1f3;
$t-section-bg-color: #ffffff;
$t-header-bg-color: #ffffff;
$t-bottom-bar-bg-color: #f1f1f3;
$t-text-color: #1a1d21;
$t-hint-color: #a8a8a8;
$t-subtitle-text-color: #82868a;
$t-accent-text-color: #1c93e3;
$t-section-header-text-color: #298acf;
$t-button-color: #229af0;
$t-button-text-color: #ffffff;
$t-link-color: #2678b6;
$t-destructive-text-color: #cc2929;
$t-section-separator-color: #d9d9d9;
$primary : $t-button-color;
$secondary : #26A69A;
$accent : #9C27B0;
@@ -20,11 +47,17 @@ $dark : #1D1D1D;
$dark-page : #121212;
$positive : #21BA45;
$negative : #C10015;
$negative : $t-destructive-text-color;
$info : #31CCEC;
$warning : #F2C037;
$lightgrey : $t-secondary-bg-color;
$brand: #419FD9;
$base-bg: #517DA2;
$typography-font-family : 'myFont', Roboto !default;
$tgcolor: #419FD9;
$body-font-size: var(--dynamic-font-size);
$typography-font-family: 'myFont', Roboto !default;

View File

@@ -1 +1 @@
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: 'Lets Fly!', banner__main_btn_description: 'Continue in Telegram', problem__title: 'Keep your workflow, boost your efficiency', problem__1: 'Unified Workspace', problem__1_description: 'Gathers tasks, meetings, and files from all your connected chats into a single, unified space.', problem__2: 'Team Directory', problem__2_description: 'Shows all members and their roles so you can instantly see who is responsible for what.', problem__3: 'Chat Integration', problem__3_description: 'Updates to tasks and meetings appear right in your chats — where the conversation happens.', problem__4: 'Smart Permissions', problem__4_description: 'Everyone sees only their relevant info. We take care of privacy and order, so you don\'t have to.', how_it_works__title: 'How it works', how_it_works__intro: 'Intro', how_it_works__admin: 'Administrator', how_it_works__user: 'Users', price__title: 'Tariff', price__subtitle: 'All plans include full functionality: an unlimited number of users, projects, tasks, and meetings.', 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__legal: 'Business & self-employed', price__individual: 'Individuals', price__rub_pay: 'Bank transfer', price__rub_resident: 'For RU residents', price__rub_closing_documents: 'Accounting documents', FAQ__title: 'Frequently Asked Questions (FAQ)', faq__question_1: 'Who is this app for?', faq__answer_1: 'For project teams conducting work communications in Telegram: IT development, construction, event industry, consulting, and others. The app creates a unified information environment and is essential when external contractors are involved and parties lack a shared infrastructure for collaboration.', faq__question_2: 'Is it mandatory to grant the bot message access and assign it as an administrator in chats?', faq__answer_2: 'No. Chats can be connected in two modes: "Private" (no message access or admin rights required) and "Full Functionality". The mode can be changed at any time. In Private mode, automatic backup of chat files and their addition to the project\'s general list is not performed.', faq__question_3: 'Can the app be connected to already existing chats?', faq__answer_3: 'Yes, any existing chat can be connected. There is also an option to share a special link with colleagues so they can connect their own chats to the project. No new groups are required — work continues within the familiar Telegram interface with added features for managing multiple project chats.', faq__question_4: 'Where are my files and data stored?', faq__answer_4: 'The app does not store chat history. Files are kept within Telegram or cloud services (Yandex Disk or Google Drive).', faq__question_5: 'Why is some contact information in the address book not displayed fully?', faq__answer_5: 'Contact details are displayed only with the user\'s consent and depend on the information provided by the administrator.', faq__question_6: 'Can participants of one project see data from another, and can one chat be linked to multiple projects?', faq__answer_6: 'A chat can be linked to only one project to maintain order. Projects are completely independent: information (chats, files, tasks, contacts) never overlaps. The app provides quick switching between workspaces, displaying only the information relevant to the currently selected project.', faq__question_7: 'What happens if I remove the app\'s bot from a chat or stop tracking in the app?', faq__answer_7: 'New messages and files will no longer be processed, and it will become impossible to attach tasks or meetings to the chat. All previously collected information is preserved and remains searchable. This allows the app to be used as an archive after a project is completed.', faq__question_8: 'How do I pay for the subscription and who pays for it?', faq__answer_8: 'Individuals pay via Telegram Stars; legal entities (RU residents) pay by invoice. Only the admin pays for the subscription—access is free for all other members.', 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__doc_consent: 'Consent to the processing of my personal data', footer__doc_subscription_guide: 'Provision on subscription tariffs', 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: 'Lets Fly!', banner__main_btn_description: 'Continue in Telegram', problem__title: 'Keep your workflow, boost your efficiency', problem__1: 'Unified Workspace', problem__1_description: 'Gathers tasks, meetings, and files from all your connected chats into a single, unified space', problem__2: 'Team Directory', problem__2_description: 'Shows all members and their roles so you can instantly see who is responsible for what', problem__3: 'Chat Integration', problem__3_description: 'Updates to tasks and meetings appear right in your chats — where the conversation happens', problem__4: 'Smart Permissions', problem__4_description: 'Everyone sees only their relevant info. We take care of privacy and order, so you don\'t have to', how_it_works__title: 'How it works', how_it_works__intro: 'Intro', how_it_works__admin: 'Administrator', how_it_works__user: 'Users', price__title: 'Tariff', price__subtitle: 'All plans include full functionality: an unlimited number of users, projects, tasks, and meetings.', 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__legal: 'Corporate', price__individual: 'Personal', price__rub_pay: 'Bank transfer', price__rub_resident: 'For RU residents', price__rub_closing_documents: 'Accounting documents', FAQ__title: 'Frequently Asked Questions (FAQ)', faq__question_1: 'Who is this app for?', faq__answer_1: 'For project teams conducting work communications in Telegram: IT development, construction, event industry, consulting, and others. The app creates a unified information environment and is essential when external contractors are involved and parties lack a shared infrastructure for collaboration.', faq__question_2: 'Is it mandatory to grant the bot message access and assign it as an administrator in chats?', faq__answer_2: 'No. Chats can be connected in two modes: "Private" (no message access or admin rights required) and "Full Functionality". The mode can be changed at any time. In Private mode, automatic backup of chat files and their addition to the project\'s general list is not performed.', faq__question_3: 'Can the app be connected to already existing chats?', faq__answer_3: 'Yes, any existing chat can be connected. There is also an option to share a special link with colleagues so they can connect their own chats to the project. No new groups are required — work continues within the familiar Telegram interface with added features for managing multiple project chats.', faq__question_4: 'Where are my files and data stored?', faq__answer_4: 'The app does not store chat history. Files are kept within Telegram or cloud services (Yandex Disk or Google Drive).', faq__question_5: 'Why is some contact information in the address book not displayed fully?', faq__answer_5: 'Contact details are displayed only with the user\'s consent and depend on the information provided by the administrator.', faq__question_6: 'Can participants of one project see data from another, and can one chat be linked to multiple projects?', faq__answer_6: 'A chat can be linked to only one project to maintain order. Projects are completely independent: information (chats, files, tasks, contacts) never overlaps. The app provides quick switching between workspaces, displaying only the information relevant to the currently selected project.', faq__question_7: 'What happens if I remove the app\'s bot from a chat or stop tracking in the app?', faq__answer_7: 'New messages and files will no longer be processed, and it will become impossible to attach tasks or meetings to the chat. All previously collected information is preserved and remains searchable. This allows the app to be used as an archive after a project is completed.', faq__question_8: 'How do I pay for the subscription and who pays for it?', faq__answer_8: 'Individuals pay via Telegram Stars; legal entities (RU residents) pay by invoice. Only the admin pays for the subscription—access is free for all other members.', 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__doc_consent: 'Consent to the processing of my personal data', footer__doc_subscription_guide: 'Provision on subscription tariffs', footer__contacts_location: 'Russia, Moscow/Voronezh', footer__description_user_data: 'The site does not collect user data, use cookies, or track user activity.', footer__contacts_ogrnip: 'PSRNSP', footer__contacts_inn: 'ITN', footer__contacts_pdn: 'Data Controller' }

File diff suppressed because one or more lines are too long

View File

@@ -9,16 +9,22 @@
<div
class="q-mx-md q-mt-lg q-py-sm q-px-md"
:class="isHeroScroll ? 'text-white' : 'text-white glass shadow-6'"
style="
transition: background-color 0.5s ease, color 0.5s ease;
border-radius: var(--base-radius)
"
:style="{
'backdrop-filter': isHeroScroll ? 'none' : 'blur(12px) saturate(180%)',
'-webkit-backdrop-filter': isHeroScroll ? 'none' : 'blur(12px) saturate(180%)',
'border-radius': 'var(--base-radius)',
'transition': 'all 0.5s ease'
}"
>
<div
ref="headerContainer"
class="flex justify-between no-wrap items-center"
>
<base-logo ref="logo" class="text-h6" />
<base-logo
ref="logo"
class="text-h5"
:text-color="isHeroScroll ? 'white' : 'primary'"
/>
<div
ref="menuContainer"
class="row items-center q-ml-md no-wrap"
@@ -113,8 +119,16 @@
id="hero_banner"
/>
<problem-section id="problems"/>
<how-works-section id="how_works" />
<price-section id="price" />
<how-works-section
id="how_works"
@update:tabs-no-caps="val => tab1 = val"
:useTabsNoCaps
/>
<price-section
id="price"
@update:tabs-no-caps="val => tab2 = val"
:useTabsNoCaps
/>
<faq-section id="FAQ" />
<footer-section id="contacts" />
</q-page>
@@ -123,7 +137,7 @@
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { ref, onMounted, nextTick, watch } from 'vue'
import { useRoute } from 'vue-router'
import { scroll } from 'quasar'
import { useI18n } from 'vue-i18n'
@@ -257,6 +271,12 @@
}, 450)
}
})
const useTabsNoCaps = ref(false)
const tab1 = ref(false)
const tab2 = ref(false)
watch([tab1, tab2], () => useTabsNoCaps.value = tab1.value || tab2.value)
</script>
<style scoped>
@@ -265,9 +285,7 @@
pointer-events: none;
}
.glass {
background-color: rgba(255, 255, 255, 0.45) !important;
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
background-color: rgba(255, 255, 255, 0.451) !important;
border: 1px solid rgba(255, 255, 255, 0.3);
}
</style>

View File

@@ -14,6 +14,9 @@
class="q-pa-md text-h4 cursor-pointer"
@click="router.push({ name: 'main' })"
/>
<div class="text-h5 text-bold">
{{ $t(getDoc(documentName)) }}
</div>
<markdown-viewver
v-if="documentName"
:locale
@@ -43,6 +46,10 @@
const router = useRouter()
const documentName = ref(null)
const getDoc = (fileName) => {
return docs.find(d => d.file === fileName)?.translationKey ?? ''
}
onMounted(() => {
const doc = docs.find(d => d.route === route.name)
if (doc) {

BIN
~$i18n-2-landing.xlsm Normal file

Binary file not shown.