Files
test/app/app.vue
2026-02-10 18:05:07 +07:00

874 lines
40 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 flow-panel rounded-3xl p-4">
<ClientOnly>
<VueFlow
:id="flowId"
class="h-full w-full"
:nodes="flowNodes"
:edges="flowEdges"
:fit-view-on-init="true"
:min-zoom="0.35"
:max-zoom="1"
: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="grid gap-10 lg:grid-cols-[1.6fr_0.7fr] items-start">
<div>
<div class="slide-head">
<h2 class="slide-title">Принцип проектирования сценария</h2>
<p class="slide-preamble">
Мы закладываем идею «один экран одно полезное действие». Пользователь всегда понимает,
на каком этапе он находится, что уже сделано и что нужно сделать дальше.
</p>
</div>
<div class="grid gap-5">
<div class="glass-card rounded-3xl p-5 shadow-soft">
<div class="text-xs font-semibold text-neutral/60">Принцип 01</div>
<div class="mt-2 font-display text-xl">Один экран одно действие</div>
</div>
<div class="glass-card rounded-3xl p-5 shadow-soft lg:translate-x-6">
<div class="text-xs font-semibold text-neutral/60">Принцип 02</div>
<div class="mt-2 font-display text-xl">Полный контекст этапа</div>
</div>
<div class="glass-card rounded-3xl p-5 shadow-soft lg:translate-x-12">
<div class="text-xs font-semibold text-neutral/60">Принцип 03</div>
<div class="mt-2 font-display text-xl">Прозрачные переходы</div>
</div>
</div>
</div>
<div class="relative flex w-full justify-center">
<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-[1.1fr_0.9fr]">
<div class="glass-card rounded-3xl border border-base-300 p-6">
<div class="text-xs font-semibold text-neutral/60">North Star Metric</div>
<div class="mt-2 font-display text-2xl">Доля этапов, выполненных в обещанный срок</div>
<p class="mt-3 text-sm text-neutral/70">
Главный показатель того, что сервис действительно улучшает предсказуемость и дисциплину сроков.
</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">Input-метрика</div>
<div class="mt-2 font-display text-lg">Ошибка прогноза (факт план)</div>
</div>
<div class="glass-card rounded-3xl border border-base-300 p-6">
<div class="text-xs font-semibold text-neutral/60">Input-метрика</div>
<div class="mt-2 font-display text-lg">Отклонение объема работ от плана</div>
</div>
<div class="glass-card rounded-3xl border border-base-300 p-6">
<div class="text-xs font-semibold text-neutral/60">Input-метрика</div>
<div class="mt-2 font-display text-lg">Фактическая длительность по типу работ</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, nextTick, ref } from 'vue'
import { VueFlow, Handle, Position, MarkerType, useVueFlow } from '@vue-flow/core'
import Granim from 'granim'
import '@vue-flow/core/dist/style.css'
import '@vue-flow/core/dist/theme-default.css'
useHead({
title: 'Контроль ремонта — презентация',
meta: [
{ name: 'description', content: 'Презентация продукта: смета, приемка, эскроу и методология ремонта.' }
]
})
let granimInstance
const flowId = 'key-user-flow'
const flowNodes = ref([
{
id: '1',
type: 'step',
position: { x: 0, y: 40 },
data: {
step: '01',
title: 'Добавление объекта',
text: 'Заказчик загружает фото/видео/обмеры или проект — система получает исходные данные.'
}
},
{
id: '2',
type: 'step',
position: { x: 200, y: 40 },
data: {
step: '02',
title: 'Смета и этапы',
text: 'Алгоритм формирует объем работ, смету, этапы и критерии приемки по каждому шагу.'
}
},
{
id: '3',
type: 'step',
position: { x: 400, y: 40 },
data: {
step: '03',
title: 'Создание заявки',
text: 'Заказчик подтверждает параметры и публикует заявку на выполнение работ.'
}
},
{
id: '4',
type: 'step',
position: { x: 600, y: 40 },
data: {
step: '04',
title: 'Выбор исполнителя',
text: 'Выбирается бригада или мастер, согласуются сроки и старт этапа.'
}
},
{
id: '5',
type: 'step',
position: { x: 800, y: 40 },
data: {
step: '05',
title: 'Эскроу и запуск',
text: 'Заказчик вносит оплату в эскроу, исполнитель запускает работы по регламенту.'
}
},
{
id: '6',
type: 'step',
position: { x: 1000, y: 40 },
data: {
step: '06',
title: 'Фиксация прогресса',
text: 'Исполнитель ведет этапы, отмечает завершение, заказчик видит статус и результаты.'
}
},
{
id: '7',
type: 'step',
position: { x: 1200, 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 }
])
const { fitView } = useVueFlow({ id: flowId })
onMounted(() => {
granimInstance = new Granim({
element: '#canvas-basic',
direction: 'radial',
isPausedWhenNotInView: true,
states: {
'default-state': {
gradients: [
['#ff9966', '#ff5e62'],
['#00F260', '#0575E6'],
['#e1eec3', '#f05053']
]
}
}
})
nextTick(() => {
fitView({ padding: 0.08 })
})
onUnmounted(() => {
if (granimInstance && granimInstance.pause) granimInstance.pause()
})
})
</script>
<style>
@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: 420px;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
position: relative;
}
.flow-panel {
border: none;
}
.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%;
}
.flow-card {
min-width: 180px;
max-width: 180px;
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: 360px;
}
.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>