Files
webapp/app/pages/index.vue

627 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
const { locale } = useI18n()
const isEn = computed(() => locale.value === 'en')
const heroTitle = computed(() => isEn.value
? 'Optovia makes procurement and logistics transparent'
: 'Optovia делает закупку и логистику прозрачными')
const howSteps = computed(() => isEn.value
? [
{
number: '01',
title: 'Choose product and destination',
text: 'Start from the top search capsule and set what you buy and where it should go.',
},
{
number: '02',
title: 'See offers and hubs on map',
text: 'Compare suppliers, hubs, prices, and route options on one interactive canvas.',
},
{
number: '03',
title: 'Commit with full context',
text: 'Move to the best option with clear terms, timeline, and next actions for your team.',
},
]
: [
{
number: '01',
title: 'Выберите товар и направление',
text: 'Начните в верхней поисковой капсуле: задайте что закупаете и куда везете.',
},
{
number: '02',
title: 'Смотрите офферы и хабы на карте',
text: 'Сравнивайте поставщиков, хабы, цены и маршруты в одном интерактивном полотне.',
},
{
number: '03',
title: 'Принимайте решение с полным контекстом',
text: 'Переходите к лучшему варианту с понятными условиями, сроками и шагами для команды.',
},
])
const services = computed(() => isEn.value
? [
{
title: 'Supplier and offer discovery',
text: 'Find relevant suppliers and compare live commercial terms without tab chaos.',
toneFrom: '#0f243d',
toneTo: '#153962',
},
{
title: 'Hub-first route strategy',
text: 'Evaluate delivery through key hubs and optimize route economics early.',
toneFrom: '#182b45',
toneTo: '#22466f',
},
{
title: 'Map-based operating control',
text: 'Keep product, destination, and route context in one place for faster execution.',
toneFrom: '#15243a',
toneTo: '#214a60',
},
{
title: 'Team workflow continuity',
text: 'Share context between buyer, operations, and manager roles without data loss.',
toneFrom: '#122033',
toneTo: '#1d3a5c',
},
]
: [
{
title: 'Поиск поставщиков и офферов',
text: 'Находите релевантных поставщиков и сравнивайте коммерцию без хаоса вкладок.',
toneFrom: '#0f243d',
toneTo: '#153962',
},
{
title: 'Маршрутная стратегия через хабы',
text: 'Оценивайте доставку через ключевые хабы и заранее оптимизируйте экономику.',
toneFrom: '#182b45',
toneTo: '#22466f',
},
{
title: 'Операционный контроль на карте',
text: 'Держите товар, направление и маршрут в одном месте для быстрого исполнения.',
toneFrom: '#15243a',
toneTo: '#214a60',
},
{
title: 'Непрерывный командный workflow',
text: 'Передавайте контекст между закупкой, операционкой и менеджментом без потерь.',
toneFrom: '#122033',
toneTo: '#1d3a5c',
},
])
const advantages = computed(() => isEn.value
? [
'Single visual language from homepage to map and cabinet.',
'Faster decisions due to shared context between roles.',
'Less operational friction in procurement and route planning.',
]
: [
'Единый визуальный контур от лендинга до карты и кабинета.',
'Быстрее решения за счет общего контекста между ролями.',
'Меньше операционных потерь в закупке и планировании маршрута.',
])
const trustedBy = [
{ name: 'Gazprom', logo: '/trust-logos/gazprom.svg' },
{ name: 'Rossiya 1', logo: '/trust-logos/russia1.svg' },
{ name: 'Absolut Bank', logo: '/trust-logos/absolutbank.svg' },
{ name: 'Kalashnikov', logo: '/trust-logos/kalashnikov.svg' },
{ name: 'Sber Logistics', logo: '/trust-logos/sberlog.svg' },
{ name: 'Dellin', logo: '/trust-logos/dellin.svg' },
{ name: 'PEK', logo: '/trust-logos/pek.svg' },
{ name: 'FESCO', logo: '/trust-logos/fesco.svg' },
] as const
const testimonials = computed(() => isEn.value
? [
{
quote: 'We reduced route decision time from days to hours because all options are visible on one map.',
author: 'Elena Morozova',
role: 'Head of Procurement Operations',
avatar: 'https://randomuser.me/api/portraits/women/44.jpg',
},
{
quote: 'The capsule search and hub view made supplier comparison much cleaner for our team.',
author: 'Dmitry Volkov',
role: 'Import Manager',
avatar: 'https://randomuser.me/api/portraits/men/52.jpg',
},
{
quote: 'Optovia removed communication noise between buyers and logistics managers.',
author: 'Alex Gromov',
role: 'CEO, Trading Company',
avatar: 'https://randomuser.me/api/portraits/men/41.jpg',
},
]
: [
{
quote: 'Скорость выбора маршрута сократилась с дней до часов, потому что все варианты видны на одной карте.',
author: 'Екатерина Морозова',
role: 'Руководитель закупочной операционки',
avatar: 'https://randomuser.me/api/portraits/women/44.jpg',
},
{
quote: 'Капсульный поиск и режим хабов сделали сравнение поставщиков заметно чище для команды.',
author: 'Дмитрий Волков',
role: 'Менеджер по импорту',
avatar: 'https://randomuser.me/api/portraits/men/52.jpg',
},
{
quote: 'Optovia убрала шум в коммуникации между закупкой и логистикой.',
author: 'Александр Громов',
role: 'CEO, торговая компания',
avatar: 'https://randomuser.me/api/portraits/men/41.jpg',
},
])
definePageMeta({
layout: 'topnav',
})
</script>
<template>
<main class="landing-page">
<section class="relative min-h-[72vh] w-full bg-gradient-to-br from-[#0b3a46] via-[#132b49] to-[#1a2a63] px-3 pb-10 pt-40 text-white md:px-4 md:pt-52">
<div class="mx-auto w-full max-w-[1280px]">
<div class="mx-auto max-w-[940px] text-center" data-landing-search-anchor>
<h1 class="text-4xl font-black leading-tight md:text-6xl">
{{ heroTitle }}
</h1>
</div>
</div>
</section>
<section class="section section--light">
<div class="section-inner">
<header class="section-header">
<h2>{{ isEn ? 'How it works' : 'Как это работает' }}</h2>
</header>
<ol class="steps-flow">
<li v-for="step in howSteps" :key="step.number" class="step-item">
<p class="step-number">{{ step.number }}</p>
<h3>{{ step.title }}</h3>
<p>{{ step.text }}</p>
</li>
</ol>
</div>
</section>
<section class="section section--dark">
<div class="section-inner">
<header class="section-header section-header--inverse">
<h2>{{ isEn ? 'Core capabilities' : 'Ключевые возможности' }}</h2>
</header>
<div class="service-stack">
<article
v-for="(service, index) in services"
:key="service.title"
class="service-lane"
:style="{ backgroundImage: `linear-gradient(110deg, ${service.toneFrom} 0%, ${service.toneTo} 100%)` }"
>
<p class="service-index">{{ String(index + 1).padStart(2, '0') }}</p>
<div>
<h3>{{ service.title }}</h3>
<p>{{ service.text }}</p>
</div>
</article>
</div>
</div>
</section>
<section class="section section--accent">
<div class="section-inner why-grid">
<div>
<h2>{{ isEn ? 'Why teams choose this model' : 'Почему команды выбирают эту модель' }}</h2>
<p class="why-lead">
{{ isEn ? 'The same interaction model works from landing entry to day-to-day map operations.' : 'Одна и та же модель взаимодействия работает от первого экрана до ежедневной работы на карте.' }}
</p>
</div>
<ul class="why-list">
<li v-for="item in advantages" :key="item">{{ item }}</li>
</ul>
</div>
</section>
<section class="section section--plain">
<div class="section-inner">
<header class="section-header">
<h2>{{ isEn ? 'Trusted by teams' : 'Нам доверяют команды' }}</h2>
</header>
<div class="logo-wall" role="list" :aria-label="isEn ? 'Client logos' : 'Логотипы клиентов'">
<figure v-for="brand in trustedBy" :key="brand.name" role="listitem" class="logo-brand">
<img :src="brand.logo" :alt="`Logo ${brand.name}`" loading="lazy" />
</figure>
</div>
</div>
</section>
<section class="section section--reviews">
<div class="section-inner">
<header class="section-header">
<h2>{{ isEn ? 'What teams say' : 'Что говорят команды' }}</h2>
</header>
<div class="review-layout">
<article class="review-main">
<div class="review-person">
<img :src="testimonials[0].avatar" :alt="testimonials[0].author" class="review-avatar review-avatar--lg" loading="lazy" />
<div>
<p class="review-name">{{ testimonials[0].author }}</p>
<p class="review-role">{{ testimonials[0].role }}</p>
</div>
</div>
<p class="review-main__quote">«{{ testimonials[0].quote }}»</p>
</article>
<div class="review-side">
<article v-for="item in testimonials.slice(1)" :key="item.author" class="review-mini">
<div class="review-person">
<img :src="item.avatar" :alt="item.author" class="review-avatar" loading="lazy" />
<div>
<p class="review-name">{{ item.author }}</p>
<p class="review-role">{{ item.role }}</p>
</div>
</div>
<p>«{{ item.quote }}»</p>
</article>
</div>
</div>
</div>
</section>
</main>
</template>
<style scoped>
.landing-page {
display: flex;
min-height: 100%;
flex-direction: column;
padding-bottom: 0;
background: #eef2f6;
}
.section {
padding: 3.25rem 0;
}
.section-inner {
margin: 0 auto;
width: 100%;
max-width: 1280px;
padding: 0 0.75rem;
}
.section-header {
margin-bottom: 1.35rem;
}
.section-header h2 {
margin: 0.3rem 0 0;
font-size: clamp(1.8rem, 4vw, 3rem);
font-weight: 900;
color: #12213a;
}
.section-header--inverse h2 {
color: #fff;
}
.section--light {
background: linear-gradient(180deg, #f1f5fb 0%, #edf3fb 100%);
}
.steps-flow {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 1rem;
}
.step-item {
position: relative;
padding: 1.2rem 0 1.2rem 4.3rem;
border-top: 1px solid #cfdae8;
}
.step-number {
position: absolute;
left: 0;
top: 0.65rem;
margin: 0;
font-size: clamp(1.8rem, 4vw, 2.7rem);
font-weight: 900;
color: #d12e35;
}
.step-item h3 {
margin: 0;
font-size: 1.35rem;
font-weight: 900;
color: #12213a;
}
.step-item p {
margin: 0.45rem 0 0;
color: #3f5673;
line-height: 1.5;
}
.section--dark {
background:
radial-gradient(circle at 90% 15%, rgba(217, 61, 67, 0.3), rgba(217, 61, 67, 0) 34%),
linear-gradient(155deg, #0b1a2f 0%, #102842 100%);
}
.service-stack {
display: grid;
gap: 0.75rem;
}
.service-lane {
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 18px;
padding: 1.15rem;
display: grid;
gap: 0.8rem;
grid-template-columns: auto 1fr;
align-items: start;
color: #fff;
}
.service-index {
margin: 0;
font-size: 1.15rem;
font-weight: 900;
color: rgba(255, 255, 255, 0.7);
}
.service-lane h3 {
margin: 0;
font-size: 1.28rem;
font-weight: 900;
}
.service-lane p {
margin: 0.45rem 0 0;
color: rgba(255, 255, 255, 0.88);
}
.section--accent {
background:
radial-gradient(circle at 6% 0%, rgba(251, 220, 207, 0.54), rgba(251, 220, 207, 0) 37%),
linear-gradient(180deg, #f8f0ea 0%, #f4ece8 100%);
}
.why-grid {
display: grid;
gap: 1.35rem;
}
.why-grid h2 {
margin: 0.3rem 0 0;
font-size: clamp(1.8rem, 4vw, 3rem);
font-weight: 900;
color: #12213a;
}
.why-lead {
margin: 1rem 0 0;
color: #3f5673;
line-height: 1.55;
}
.why-list {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 1rem;
}
.why-list li {
padding: 0.2rem 0 0.2rem 2.3rem;
color: #1d2f49;
font-weight: 700;
position: relative;
line-height: 1.45;
}
.why-list li::before {
content: '✓';
position: absolute;
left: 0;
top: 0.05rem;
width: 1.55rem;
height: 1.55rem;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
background: #1f8a5a;
color: #fff;
font-size: 0.9rem;
font-weight: 900;
}
.section--plain {
background: #f7f9fc;
}
.logo-wall {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.8rem;
}
.logo-brand {
margin: 0;
min-height: 84px;
border-radius: 20px;
border: 1px solid #d4dde8;
background: linear-gradient(180deg, #ffffff 0%, #f2f6fb 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 0.75rem;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.logo-brand img {
width: min(88%, 240px);
height: auto;
object-fit: contain;
}
.section--reviews {
background: linear-gradient(180deg, #edf3fb 0%, #e9f0f8 100%);
flex: 1 0 auto;
padding-bottom: 0;
margin-bottom: 0;
}
.review-layout {
display: grid;
gap: 0.8rem;
}
.review-main {
border-radius: 24px;
padding: 1.3rem;
background: linear-gradient(145deg, #14253d 0%, #1c3b5e 100%);
color: #fff;
display: grid;
align-content: start;
gap: 0.9rem;
}
.review-person {
display: flex;
align-items: center;
gap: 0.75rem;
}
.review-avatar {
width: 2.75rem;
height: 2.75rem;
border-radius: 999px;
object-fit: cover;
border: 2px solid rgba(255, 255, 255, 0.44);
}
.review-avatar--lg {
width: 3.2rem;
height: 3.2rem;
}
.review-name {
margin: 0;
font-size: 0.95rem;
font-weight: 800;
color: #ffffff;
}
.review-role {
margin: 0.1rem 0 0;
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.82);
}
.review-main__quote {
margin: 0;
color: #fff;
font-size: clamp(1.2rem, 2.6vw, 1.6rem);
line-height: 1.45;
}
.review-side {
display: grid;
gap: 0.8rem;
}
.review-mini {
border-left: 4px solid #d12e35;
padding: 0.75rem 0.9rem;
background: rgba(255, 255, 255, 0.65);
}
.review-mini > p {
margin: 0.55rem 0 0;
color: #27405f;
}
.review-mini .review-name {
color: #1e3555;
}
.review-mini .review-role {
color: #4f6581;
}
@media (min-width: 768px) {
.section {
padding: 4.5rem 0;
}
.section-inner {
padding: 0 1rem;
}
.steps-flow {
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1.5rem;
}
.step-item {
border-top: 0;
border-left: 1px solid #cfdae8;
padding: 1rem 0 1rem 1.7rem;
}
.step-number {
position: static;
margin-bottom: 0.75rem;
}
.why-grid {
grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
align-items: start;
}
.why-list {
grid-template-columns: repeat(3, minmax(0, 1fr));
align-items: stretch;
gap: 1.35rem;
}
.logo-wall {
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1rem 1.2rem;
}
.review-layout {
grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
gap: 1.2rem;
}
}
@media (max-width: 1023px) {
.landing-page {
padding-bottom: 0;
}
}
</style>