Files
test/app/app.vue
2026-02-10 17:40:47 +07:00

853 lines
39 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.

<template>
<div data-theme="silk" class="min-h-screen bg-mesh text-neutral">
<canvas id="canvas-basic" class="gradient-canvas" aria-hidden="true"></canvas>
<div class="relative z-10">
<div
class="pointer-events-none absolute -top-24 left-1/2 h-72 w-[1100px] -translate-x-1/2 rounded-full bg-[radial-gradient(circle_at_center,rgba(67,56,118,0.25),transparent_70%)] blur-3xl"
></div>
<main class="relative px-6 pb-16 pt-10 md:px-12 lg:px-20">
<div class="grid gap-8 lg:grid-cols-[220px_1fr]">
<aside class="hidden lg:block">
<div class="glass-panel sticky top-10 rounded-3xl border border-base-300 p-5 shadow-soft">
<nav class="space-y-2 text-sm">
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#context"><span class="nav-num">01</span><span class="nav-text">Контекст</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#alternatives"><span class="nav-num">02</span><span class="nav-text">Альтернативы</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#problem"><span class="nav-num">03</span><span class="nav-text">Проблемы</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#core"><span class="nav-num">04</span><span class="nav-text">Core-фича</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#flow"><span class="nav-num">05</span><span class="nav-text">Key user flow</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#value"><span class="nav-num">06</span><span class="nav-text">Value Proposition</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#prototype"><span class="nav-num">07</span><span class="nav-text">Прототип</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#monetization"><span class="nav-num">08</span><span class="nav-text">Монетизация</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#metrics"><span class="nav-num">09</span><span class="nav-text">Метрики</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#roadmap"><span class="nav-num">10</span><span class="nav-text">Roadmap</span></a>
<a class="nav-link block rounded-2xl px-3 py-2 text-neutral/70 border border-transparent" href="#final"><span class="nav-num">11</span><span class="nav-text">Контакты</span></a>
</nav>
</div>
</aside>
<div class="space-y-10">
<section id="context" class="slide">
<div class="slide-top"><span class="slide-num">01</span><span class="slide-label">Контекст</span></div>
<div class="slide-head">
<h2 class="slide-title">Ремонт редкий и дорогой опыт</h2>
<p class="slide-preamble">
Большинство людей проходят ремонт 12 раза в жизни. Это эмоционально тяжелый процесс с
высокими ставками, где нет навыка управления и нет стандарта качества. Поэтому каждый
проект начинается заново: ожидания не совпадают, правила не зафиксированы, а неопределенность
быстро превращается в конфликт. Заказчик хочет контроля и понятных правил, исполнитель ясных
критериев и гарантии оплаты.
</p>
</div>
<div class="slide-main"></div>
</section>
<section id="alternatives" class="slide">
<div class="slide-top"><span class="slide-num">02</span><span class="slide-label">Альтернативы</span></div>
<div class="slide-head">
<h2 class="slide-title">Сегодня все держится на «костылях»</h2>
<p class="slide-preamble">
Пока нет единого стандарта, люди собирают процесс из разрозненных инструментов. Это
разрывает коммуникацию, создает разные версии правды и делает контроль почти невозможным.
</p>
</div>
<div class="slide-main">
<div class="grid gap-6 md:grid-cols-3 md:items-end">
<div class="glass-card rounded-3xl p-6 shadow-soft md:translate-y-6">
<h3 class="font-display text-2xl">Мессенджеры</h3>
<p class="mt-3 text-sm text-neutral/70">
Фото и обсуждения разъезжаются по чатам, решения теряются в переписке.
</p>
</div>
<div class="glass-card rounded-3xl p-6 shadow-soft md:translate-y-12">
<h3 class="font-display text-2xl">Таблицы и документы</h3>
<p class="mt-3 text-sm text-neutral/70">
Сметы и списки ведутся вручную, версии расходятся, правки не фиксируются.
</p>
</div>
<div class="glass-card rounded-3xl p-6 shadow-soft">
<h3 class="font-display text-2xl">Фотоархивы и звонки</h3>
<p class="mt-3 text-sm text-neutral/70">
Статусы передаются голосом, нет доказуемых критериев качества.
</p>
</div>
</div>
</div>
</section>
<section id="problem" class="slide">
<div class="slide-top"><span class="slide-num">03</span><span class="slide-label">Проблемы</span></div>
<div class="slide-head">
<h2 class="slide-title">Ключевые проблемы</h2>
<p class="slide-preamble">
Мы провели интервью с заказчиками и мастерами и увидели, что проблемы повторяются из проекта
в проект. На практике это всегда упирается в несколько узлов, которые ломают доверие и сроки.
Ниже три ключевых вывода.
</p>
</div>
<div class="slide-main">
<div class="grid gap-6 md:grid-cols-3 md:items-end">
<div class="glass-card rounded-3xl p-6 shadow-soft md:translate-y-8">
<h3 class="font-display text-2xl">Ожидания и реальность расходятся</h3>
<p class="mt-3 text-sm text-neutral/70">
Нет общего понимания результата: стороны по-разному трактуют «готово» и «качественно».
</p>
<p class="mt-3 text-xs text-neutral/60">Заказчик: не уверен, что «сделано правильно». Исполнитель: не понимает, как это доказать.</p>
</div>
<div class="glass-card rounded-3xl p-6 shadow-soft md:translate-y-14">
<h3 class="font-display text-2xl">Критерии не зафиксированы</h3>
<p class="mt-3 text-sm text-neutral/70">
Нет списка требований, допусков и контрольных точек спор легко уходит в эмоции.
</p>
<p class="mt-3 text-xs text-neutral/60">Заказчик: нет прозрачной проверки. Исполнитель: нет единого регламента.</p>
</div>
<div class="glass-card rounded-3xl p-6 shadow-soft md:translate-y-4">
<h3 class="font-display text-2xl">Финансовый риск у обеих сторон</h3>
<p class="mt-3 text-sm text-neutral/70">
Платежи завязаны на доверии, а не на факте качества обе стороны боятся потерь.
</p>
<p class="mt-3 text-xs text-neutral/60">Заказчик: боится переплатить. Исполнитель: боится не получить деньги.</p>
</div>
</div>
</div>
</section>
<section id="core" class="slide">
<div class="slide-top"><span class="slide-num">04</span><span class="slide-label">Core-фича</span></div>
<div class="slide-head">
<h2 class="slide-title">Ноу-хау: приемка как сервис</h2>
<p class="slide-preamble">
Проанализировав проблемы, мы видим ключевую боль именно в приемке. Когда заранее понятно,
что такое хорошо и что делать дальше в каждом исходе, исчезает источник конфликта. Это и есть
наша killer-feature: критерии фиксируются до старта, а оценка становится независимой.
</p>
</div>
<div class="slide-main">
<div class="glass-panel rounded-3xl border border-base-300 p-6">
<div class="text-xs font-semibold text-neutral/60">Пример регламентной приемки</div>
<div class="mt-4 grid grid-cols-[1.1fr_1.3fr_0.9fr_1fr] gap-3 text-sm">
<div class="rounded-2xl bg-base-200 px-4 py-3 font-semibold text-neutral/70">Работа</div>
<div class="rounded-2xl bg-base-200 px-4 py-3 font-semibold text-neutral/70">Измерено</div>
<div class="rounded-2xl bg-base-200 px-4 py-3 font-semibold text-neutral/70 text-center">Оценка</div>
<div class="rounded-2xl bg-base-200 px-4 py-3 font-semibold text-neutral/70">Решение</div>
<div class="rounded-2xl bg-white px-4 py-3">Укладка плитки</div>
<div class="rounded-2xl bg-white px-4 py-3">Отклонение 1 мм при норме 2 мм</div>
<div class="rounded-2xl bg-success/15 px-4 py-3 text-center text-success">Ок</div>
<div class="rounded-2xl bg-white px-4 py-3">Оплата 100%</div>
<div class="rounded-2xl bg-white px-4 py-3">Электронный уровень</div>
<div class="rounded-2xl bg-white px-4 py-3">Отклонение 2° при норме 1°</div>
<div class="rounded-2xl bg-warning/15 px-4 py-3 text-center text-warning">На грани</div>
<div class="rounded-2xl bg-white px-4 py-3">Удержать 5%</div>
<div class="rounded-2xl bg-white px-4 py-3">Швы</div>
<div class="rounded-2xl bg-white px-4 py-3">Разбег 2 мм при норме 1 мм</div>
<div class="rounded-2xl bg-error/15 px-4 py-3 text-center text-error">Не ок</div>
<div class="rounded-2xl bg-white px-4 py-3">Переделка участка</div>
</div>
</div>
</div>
</section>
<section id="flow" class="slide flow-slide">
<div class="slide-top"><span class="slide-num">05</span><span class="slide-label">Key user flow</span></div>
<div class="slide-head">
<h2 class="slide-title">Как это работает: key user flow</h2>
<p class="slide-preamble">
Описываем ключевой путь пользователя от старта этапа до приемки: где возникают трения и
как сервис их снимает через регламент, доказуемую проверку и эскроу.
</p>
</div>
<div class="slide-main flow-main">
<div class="flow-wrapper glass-panel rounded-3xl border border-base-300 p-4">
<ClientOnly>
<VueFlow
:nodes="flowNodes"
:edges="flowEdges"
:fit-view-on-init="true"
:nodes-draggable="true"
:nodes-connectable="false"
:elements-selectable="true"
:zoom-on-scroll="false"
:zoom-on-pinch="false"
:pan-on-drag="false"
:pan-on-scroll="false"
>
<template #node-step="{ data }">
<div class="flow-card glass-card rounded-3xl p-5 shadow-soft">
<div class="text-xs font-semibold text-neutral/60">{{ data.step }}</div>
<div class="mt-2 font-display text-lg">{{ data.title }}</div>
<div class="mt-3 text-sm text-neutral/70">{{ data.text }}</div>
<Handle id="target-l" type="target" :position="Position.Left" class="flow-handle" />
<Handle id="target-r" type="target" :position="Position.Right" class="flow-handle" />
<Handle id="target-t" type="target" :position="Position.Top" class="flow-handle" />
<Handle id="source-r" type="source" :position="Position.Right" class="flow-handle" />
<Handle id="source-l" type="source" :position="Position.Left" class="flow-handle" />
<Handle id="source-b" type="source" :position="Position.Bottom" class="flow-handle" />
</div>
</template>
</VueFlow>
</ClientOnly>
</div>
</div>
</section>
<section id="value" class="slide">
<div class="slide-top"><span class="slide-num">06</span><span class="slide-label">Value Proposition</span></div>
<div class="slide-head">
<h2 class="slide-title">Что получает каждая сторона</h2>
<p class="slide-preamble">
Value Proposition: прозрачный регламент + независимая приемка = доверие между сторонами.
Мы фиксируем ценность отдельно для заказчика и исполнителя, чтобы не было перекоса.
</p>
</div>
<div class="slide-main">
<div class="grid gap-6 md:grid-cols-2">
<div class="glass-card rounded-3xl p-6 shadow-soft">
<h3 class="font-display text-2xl">Заказчик</h3>
<p class="mt-3 text-sm text-neutral/70">
Прозрачная смета, понятные критерии качества и независимая приемка. Деньги защищены
эскроу, а результат подтвержден экспертом.
</p>
</div>
<div class="glass-card rounded-3xl p-6 shadow-soft">
<h3 class="font-display text-2xl">Исполнитель</h3>
<p class="mt-3 text-sm text-neutral/70">
Четкий регламент работ, меньше спорных ситуаций и гарантированная оплата после проверки.
Проще сдавать этапы и выстраивать доверие.
</p>
</div>
</div>
</div>
</section>
<section id="prototype" class="slide">
<div class="slide-top"><span class="slide-num">07</span><span class="slide-label">Прототип</span></div>
<div class="slide-head">
<h2 class="slide-title">Один экран один смысл</h2>
<p class="slide-preamble">
Посмотрите прототип: мы подготовили сценарий, который показывает, как пользователь проходит
путь от объекта до понятной сметы, этапов и уведомлений. Окунитесь в то, как это может выглядеть.
</p>
</div>
<div class="slide-main items-end">
<div class="relative flex w-full justify-end">
<div class="iphone-frame shadow-glow">
<span class="iphone-button iphone-button--mute" aria-hidden="true"></span>
<span class="iphone-button iphone-button--vol-up" aria-hidden="true"></span>
<span class="iphone-button iphone-button--vol-down" aria-hidden="true"></span>
<span class="iphone-button iphone-button--power" aria-hidden="true"></span>
<div class="iphone-screen">
<div class="iphone-notch" aria-hidden="true"></div>
<iframe
class="iphone-iframe"
title="Прототип пользовательского пути"
src="/prototype.html"
></iframe>
</div>
</div>
</div>
</div>
</section>
<section id="monetization" class="slide">
<div class="slide-top"><span class="slide-num">08</span><span class="slide-label">Монетизация</span></div>
<div class="slide-head">
<h2 class="slide-title">Прозрачные источники выручки</h2>
<p class="slide-preamble">
Мы фиксируем этапы, сроки и материалы значит, точно знаем, где создается ценность. Это
делает монетизацию понятной для обеих сторон и логично вытекающей из процесса.
</p>
</div>
<div class="slide-main">
<div class="grid gap-6 md:grid-cols-3">
<div class="glass-card rounded-3xl p-6 shadow-soft">
<div class="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/15 text-primary">
<svg viewBox="0 0 24 24" class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.6">
<path d="M12 3v18M7 7h5a3 3 0 1 1 0 6H9a3 3 0 1 0 0 6h5" />
</svg>
</div>
<h3 class="font-display text-2xl">Комиссия эскроу</h3>
<p class="mt-3 text-sm text-neutral/70">Процент от подтвержденного этапа, удерживается сервисом.</p>
</div>
<div class="glass-card rounded-3xl p-6 shadow-soft">
<div class="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/15 text-primary">
<svg viewBox="0 0 24 24" class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.6">
<path d="M4 20h16M6 20l2-10h8l2 10M9 10V6a3 3 0 0 1 6 0v4" />
</svg>
</div>
<h3 class="font-display text-2xl">Инспекция</h3>
<p class="mt-3 text-sm text-neutral/70">Плата за выезд и экспертизу, инициирует заказчик.</p>
</div>
<div class="glass-card rounded-3xl p-6 shadow-soft">
<div class="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/15 text-primary">
<svg viewBox="0 0 24 24" class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.6">
<path d="M4 7h16l-2 10H6L4 7z" />
<path d="M8 7V5a4 4 0 0 1 8 0v2" />
</svg>
</div>
<h3 class="font-display text-2xl">Материалы</h3>
<p class="mt-3 text-sm text-neutral/70">За материалы платит заказчик, сервис получает маржу.</p>
</div>
</div>
</div>
</section>
<section id="metrics" class="slide">
<div class="slide-top"><span class="slide-num">09</span><span class="slide-label">Метрики</span></div>
<div class="slide-head">
<h2 class="slide-title">Проверяем, что продукт работает</h2>
<p class="slide-preamble">
Метрики должны показать, что сервис снижает конфликт и ускоряет приемку. Мы отслеживаем
ключевые сигналы качества, скорости и точности.
</p>
</div>
<div class="slide-main">
<div class="grid gap-6 md:grid-cols-[1fr_1fr]">
<div>
<h3 class="font-display text-2xl">North Star Metric</h3>
<p class="mt-3 text-sm text-neutral/70">Доля этапов, выполненных в обещанный срок.</p>
<p class="mt-3 text-sm text-neutral/70">
Input-метрики: ошибка прогноза по срокам (факт минус план), отклонение объема работ от плана,
фактическая длительность по типу работ (средние значения).
</p>
</div>
<div class="grid gap-4">
<div class="glass-card rounded-3xl border border-base-300 p-6">
<div class="text-xs font-semibold text-neutral/60">Качество</div>
<div class="mt-2 font-display text-xl">Доля зеленых приемок</div>
</div>
<div class="glass-card rounded-3xl border border-base-300 p-6">
<div class="text-xs font-semibold text-neutral/60">Скорость</div>
<div class="mt-2 font-display text-xl">Среднее время приемки этапа</div>
</div>
<div class="glass-card rounded-3xl border border-base-300 p-6">
<div class="text-xs font-semibold text-neutral/60">Точность</div>
<div class="mt-2 font-display text-xl">Отклонение сметы от факта</div>
</div>
</div>
</div>
</div>
</section>
<section id="roadmap" class="slide">
<div class="slide-top"><span class="slide-num">10</span><span class="slide-label">Roadmap</span></div>
<div class="slide-head">
<h2 class="slide-title">Roadmap проекта</h2>
<p class="slide-preamble">
Последовательно собираем функциональность: сначала фиксируем структуру работ и правила выполнения,
затем подключаем ресурсную ведомость и стоимость, и в конце добавляем приемку с инспектором и эскроу-оплатой.
Это все один пилот, просто с ростом зрелости функционала.
</p>
</div>
<div class="slide-main">
<div class="glass-panel rounded-3xl border border-base-300 p-6">
<div class="grid gap-4 text-sm">
<div class="grid grid-cols-[260px_repeat(6,minmax(0,1fr))] gap-2 text-xs text-neutral/50">
<div>Период</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
</div>
<div class="grid grid-cols-[260px_1fr] items-center gap-4">
<div class="text-neutral/70">Этапы работ, декомпозиция объема, сроки, чек-листы, подбор исполнителей</div>
<div class="gantt-track">
<div class="gantt-bar" style="left: 0%; width: 33.333%;"></div>
</div>
</div>
<div class="grid grid-cols-[260px_1fr] items-center gap-4">
<div class="text-neutral/70">Ресурсная ведомость, материалы, стоимость работ, итоговая смета</div>
<div class="gantt-track">
<div class="gantt-bar" style="left: 33.333%; width: 33.333%;"></div>
</div>
</div>
<div class="grid grid-cols-[260px_1fr] items-center gap-4">
<div class="text-neutral/70">Приемка с инспектором, критерии качества, эскроу-оплата</div>
<div class="gantt-track">
<div class="gantt-bar" style="left: 66.666%; width: 33.333%;"></div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="final" class="slide">
<div class="slide-top"><span class="slide-num">11</span><span class="slide-label">Контакты</span></div>
<div class="slide-main items-center justify-center text-center">
<div class="flex flex-col items-center gap-4">
<img src="/avatar.jpg" alt="Руслан Бакиев" class="h-28 w-28 rounded-full object-cover ring-2 ring-base-300" />
<div class="font-display text-3xl text-neutral">Спасибо за внимание</div>
<div class="text-neutral/70">
<div class="text-lg text-neutral">Руслан Бакиев</div>
<a class="telegram-btn mt-2 inline-flex items-center gap-2" href="https://t.me/veikab" target="_blank" rel="noopener noreferrer">
<svg viewBox="0 0 24 24" class="h-4 w-4" fill="currentColor" aria-hidden="true">
<path d="M21.8 2.2a1.5 1.5 0 0 0-1.6-.2L3.6 9.3a1.5 1.5 0 0 0 .2 2.8l4.6 1.5 1.7 5.1a1.5 1.5 0 0 0 2.5.6l3.1-3 4.4 3.2a1.5 1.5 0 0 0 2.4-.9l3.2-14.2a1.5 1.5 0 0 0-.3-1.2ZM9.9 13.2l7.8-7.1-6.3 8.4-.3 3.5-1.4-4.2-3.6-1.2 10.8-4.3-7 5Z"/>
</svg>
<span>Телеграм: veikab</span>
</a>
</div>
</div>
</div>
</section>
</div>
</div>
</main>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { VueFlow, Handle, Position, MarkerType } from '@vue-flow/core'
import Granim from 'granim'
useHead({
title: 'Контроль ремонта — презентация',
meta: [
{ name: 'description', content: 'Презентация продукта: смета, приемка, эскроу и методология ремонта.' }
]
})
let granimInstance
const flowNodes = ref([
{
id: '1',
type: 'step',
position: { x: 0, y: 40 },
data: {
step: '01',
title: 'Добавление объекта',
text: 'Заказчик загружает фото/видео/обмеры или проект — система получает исходные данные.'
}
},
{
id: '2',
type: 'step',
position: { x: 180, y: 40 },
data: {
step: '02',
title: 'Смета и этапы',
text: 'Алгоритм формирует объем работ, смету, этапы и критерии приемки по каждому шагу.'
}
},
{
id: '3',
type: 'step',
position: { x: 360, y: 40 },
data: {
step: '03',
title: 'Создание заявки',
text: 'Заказчик подтверждает параметры и публикует заявку на выполнение работ.'
}
},
{
id: '4',
type: 'step',
position: { x: 540, y: 40 },
data: {
step: '04',
title: 'Выбор исполнителя',
text: 'Выбирается бригада или мастер, согласуются сроки и старт этапа.'
}
},
{
id: '5',
type: 'step',
position: { x: 720, y: 40 },
data: {
step: '05',
title: 'Эскроу и запуск',
text: 'Заказчик вносит оплату в эскроу, исполнитель запускает работы по регламенту.'
}
},
{
id: '6',
type: 'step',
position: { x: 900, y: 40 },
data: {
step: '06',
title: 'Фиксация прогресса',
text: 'Исполнитель ведет этапы, отмечает завершение, заказчик видит статус и результаты.'
}
},
{
id: '7',
type: 'step',
position: { x: 1080, y: 40 },
data: {
step: '07',
title: 'Приемка и расчет',
text: 'Инспектор сверяет критерии: если все ок — эскроу разблокируется, если нет — доработка или удержание.'
}
}
])
const flowEdges = ref([
{ id: 'e1-2', source: '1', target: '2', sourceHandle: 'source-r', targetHandle: 'target-l', type: 'straight', markerEnd: MarkerType.ArrowClosed },
{ id: 'e2-3', source: '2', target: '3', sourceHandle: 'source-r', targetHandle: 'target-l', type: 'straight', markerEnd: MarkerType.ArrowClosed },
{ id: 'e3-4', source: '3', target: '4', sourceHandle: 'source-r', targetHandle: 'target-l', type: 'straight', markerEnd: MarkerType.ArrowClosed },
{ id: 'e4-5', source: '4', target: '5', sourceHandle: 'source-r', targetHandle: 'target-l', type: 'straight', markerEnd: MarkerType.ArrowClosed },
{ id: 'e5-6', source: '5', target: '6', sourceHandle: 'source-r', targetHandle: 'target-l', type: 'straight', markerEnd: MarkerType.ArrowClosed },
{ id: 'e6-7', source: '6', target: '7', sourceHandle: 'source-r', targetHandle: 'target-l', type: 'straight', markerEnd: MarkerType.ArrowClosed }
])
onMounted(() => {
granimInstance = new Granim({
element: '#canvas-basic',
direction: 'radial',
isPausedWhenNotInView: true,
states: {
'default-state': {
gradients: [
['#ff9966', '#ff5e62'],
['#00F260', '#0575E6'],
['#e1eec3', '#f05053']
]
}
}
})
onUnmounted(() => {
if (granimInstance && granimInstance.pause) granimInstance.pause()
})
})
</script>
<style>
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;600;700&family=Space+Grotesk:wght@400;600;700&display=swap');
:root {
color-scheme: light;
}
body {
font-family: 'Manrope', system-ui, sans-serif;
}
html {
scroll-behavior: smooth;
}
.font-display {
font-family: 'Space Grotesk', system-ui, sans-serif;
}
.gradient-canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
filter: saturate(1.1);
}
.flow-wrapper {
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
position: relative;
}
.flow-wrapper::-webkit-scrollbar {
height: 8px;
}
.flow-wrapper::-webkit-scrollbar-track {
background: rgba(20, 20, 20, 0.06);
border-radius: 999px;
}
.flow-wrapper::-webkit-scrollbar-thumb {
background: rgba(20, 20, 20, 0.2);
border-radius: 999px;
}
.flow-wrapper .vue-flow {
width: 100%;
height: 100%;
}
.vue-flow {
position: relative;
}
.vue-flow__nodes {
position: absolute;
top: 0;
left: 0;
}
.vue-flow__edges {
position: absolute;
inset: 0;
}
.flow-card {
min-width: 160px;
max-width: 160px;
background: rgba(255, 255, 255, 0.78);
border: 1px solid rgba(20, 20, 20, 0.12);
box-shadow: 0 14px 30px rgba(20, 20, 20, 0.14);
}
.flow-handle {
opacity: 0;
}
.vue-flow__edge-path {
stroke: rgba(20, 20, 20, 0.45);
stroke-width: 1.4;
}
.vue-flow__edge-marker path {
fill: rgba(20, 20, 20, 0.6);
}
.nav-link {
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 6px;
}
.nav-link:hover {
background: rgba(255, 255, 255, 0.42);
border-color: rgba(255, 255, 255, 0.45);
backdrop-filter: blur(16px) saturate(1.05);
-webkit-backdrop-filter: blur(16px) saturate(1.05);
color: rgba(20, 20, 20, 0.85);
}
.nav-num {
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: rgba(20, 20, 20, 0.4);
width: 22px;
}
.nav-text {
flex: 1;
}
.slide {
border-radius: 2rem;
background: color-mix(in oklab, white 80%, transparent);
border: 1px solid rgba(20, 20, 20, 0.08);
padding: 2.5rem;
box-shadow: 0 18px 50px rgba(20, 20, 20, 0.08);
min-height: 70vh;
display: flex;
flex-direction: column;
}
.slide-top {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.2em;
margin-bottom: 1.25rem;
}
.slide-num {
color: rgba(20, 20, 20, 0.35);
}
.slide-label {
color: rgba(20, 20, 20, 0.6);
}
.telegram-btn {
padding: 8px 14px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.5);
background: rgba(255, 255, 255, 0.45);
backdrop-filter: blur(14px) saturate(1.1);
-webkit-backdrop-filter: blur(14px) saturate(1.1);
color: rgba(20, 20, 20, 0.85);
font-size: 0.85rem;
transition: all 0.2s ease;
}
.telegram-btn:hover {
background: rgba(255, 255, 255, 0.65);
border-color: rgba(255, 255, 255, 0.7);
}
.slide-title {
font-family: 'Space Grotesk', system-ui, sans-serif;
font-size: clamp(2rem, 3vw, 2.75rem);
line-height: 1.1;
margin: 0;
}
.slide-preamble {
margin-top: 0.75rem;
color: rgba(20, 20, 20, 0.7);
font-size: 1rem;
max-width: 52rem;
}
.slide-head {
margin-bottom: 1.5rem;
}
.slide-main {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1.5rem;
}
.flow-slide .slide-main {
flex: 1;
min-height: 420px;
}
.flow-main .flow-wrapper {
height: 100%;
min-height: 420px;
}
.glass-panel {
background: rgba(255, 255, 255, 0.55);
border: 1px solid rgba(255, 255, 255, 0.5);
backdrop-filter: blur(24px) saturate(1.15);
-webkit-backdrop-filter: blur(24px) saturate(1.15);
}
.gantt-track {
position: relative;
height: 18px;
border-radius: 999px;
background:
linear-gradient(to right, rgba(20, 20, 20, 0.1) 1px, transparent 1px) 0 0 / calc(100% / 6) 100%,
rgba(20, 20, 20, 0.06);
overflow: hidden;
}
.gantt-bar {
position: absolute;
top: 2px;
height: 14px;
border-radius: 999px;
background: linear-gradient(90deg, rgba(67, 56, 118, 0.9), rgba(102, 126, 234, 0.85));
box-shadow: 0 6px 14px rgba(67, 56, 118, 0.25);
}
.glass-card {
background: rgba(255, 255, 255, 0.42);
border: 1px solid rgba(255, 255, 255, 0.45);
backdrop-filter: blur(16px) saturate(1.05);
-webkit-backdrop-filter: blur(16px) saturate(1.05);
}
.flow-card.glass-card {
background: rgba(255, 255, 255, 0.78);
border: 1px solid rgba(20, 20, 20, 0.12);
box-shadow: 0 14px 30px rgba(20, 20, 20, 0.14);
}
.iphone-frame {
position: relative;
width: min(330px, 90vw);
aspect-ratio: 9 / 19.5;
padding: 14px;
border-radius: 44px;
background: linear-gradient(180deg, #141418, #0b0b0e);
box-shadow:
0 28px 60px rgba(9, 9, 12, 0.35),
inset 0 0 0 1px rgba(255, 255, 255, 0.08);
pointer-events: auto;
}
.iphone-frame::before {
content: '';
position: absolute;
inset: 6px;
border-radius: 38px;
border: 1px solid rgba(255, 255, 255, 0.08);
pointer-events: none;
}
.iphone-screen {
position: relative;
height: 100%;
border-radius: 34px;
overflow: hidden;
background: #0b0b0e;
}
.iphone-iframe {
width: 100%;
height: 100%;
border: 0;
display: block;
position: relative;
z-index: 1;
pointer-events: auto;
}
.iphone-notch {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 42%;
height: 26px;
border-radius: 0 0 20px 20px;
background: #0b0b0e;
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.35);
z-index: 2;
pointer-events: none;
}
.iphone-button {
position: absolute;
width: 3px;
border-radius: 999px;
background: #101014;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.iphone-button--mute {
height: 22px;
left: -4px;
top: 78px;
}
.iphone-button--vol-up {
height: 44px;
left: -4px;
top: 112px;
}
.iphone-button--vol-down {
height: 44px;
left: -4px;
top: 164px;
}
.iphone-button--power {
height: 60px;
right: -4px;
top: 140px;
}
</style>