Merge branch 'funnel' into codex/complete-admin-panel-for-funnels-jbwsju
This commit is contained in:
commit
788cffb228
232
docs/templates-and-builder.md
Normal file
232
docs/templates-and-builder.md
Normal file
@ -0,0 +1,232 @@
|
||||
# Шаблоны экранов и конструктор воронки
|
||||
|
||||
Этот документ описывает, из каких частей состоит JSON-конфигурация воронки, какие шаблоны экранов доступны в рантайме и как с ними работает конструктор (builder). Используйте его как справочник при ручном редактировании JSON или при настройке воронки через интерфейс администратора.
|
||||
|
||||
## Архитектура воронки
|
||||
|
||||
Воронка описывается объектом `FunnelDefinition` и состоит из двух частей: метаданных и списка экранов. Навигация осуществляется по идентификаторам экранов, а состояние (выборы пользователя) хранится отдельно в рантайме.
|
||||
|
||||
```ts
|
||||
interface FunnelDefinition {
|
||||
meta: {
|
||||
id: string;
|
||||
version?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
firstScreenId?: string; // стартовый экран, по умолчанию первый в списке
|
||||
};
|
||||
defaultTexts?: {
|
||||
nextButton?: string;
|
||||
continueButton?: string;
|
||||
};
|
||||
screens: ScreenDefinition[]; // набор экранов разных шаблонов
|
||||
}
|
||||
```
|
||||
|
||||
Каждый экран обязан иметь уникальный `id` и поле `template`, которое выбирает шаблон визуализации. Дополнительно поддерживаются:
|
||||
|
||||
- `header` — управляет прогресс-баром, заголовком и кнопкой «Назад». По умолчанию шапка показывается, а прогресс вычисляется автоматически в рантайме.
|
||||
- `bottomActionButton` — универсальное описание основной кнопки («Продолжить», «Далее» и т. п.). Шаблон может переопределить или скрыть её.
|
||||
- `navigation` — правила переходов между экранами.
|
||||
|
||||
### Навигация
|
||||
|
||||
Навигация описывается объектом `NavigationDefinition`:
|
||||
|
||||
```ts
|
||||
interface NavigationDefinition {
|
||||
defaultNextScreenId?: string; // переход по умолчанию
|
||||
rules?: Array<{
|
||||
nextScreenId: string; // куда перейти, если условие выполнено
|
||||
conditions: Array<{
|
||||
screenId: string; // экран, чьи ответы проверяем
|
||||
operator?: "includesAny" | "includesAll" | "includesExactly";
|
||||
optionIds: string[]; // выбранные опции, которые проверяются
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
Рантайм использует первый сработавший `rule` и только после этого обращается к `defaultNextScreenId`. Для списков с одиночным выбором и скрытой кнопкой переход совершается автоматически при изменении ответа. Для всех прочих шаблонов пользователь должен нажать действие, сконфигурированное для текущего экрана.
|
||||
|
||||
## Шаблоны экранов
|
||||
|
||||
Ниже приведено краткое описание каждого шаблона и JSON-поле, которое его конфигурирует.
|
||||
|
||||
### Информационный экран (`template: "info"`)
|
||||
|
||||
Используется для показа статических сообщений, промо-блоков или инструкций. Обязательные поля — `id`, `template`, `title`. Дополнительно поддерживаются:
|
||||
|
||||
- `description` — расширенный текст под заголовком.
|
||||
- `icon` — эмодзи или картинка. `type` принимает значения `emoji` или `image`, `value` — символ или URL, `size` — `sm | md | lg | xl`.
|
||||
- `bottomActionButton` — описание кнопки внизу, если нужно отличное от дефолтного текста.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "welcome",
|
||||
"template": "info",
|
||||
"title": { "text": "Добро пожаловать" },
|
||||
"description": { "text": "Заполните короткую анкету, чтобы получить персональное предложение." },
|
||||
"icon": { "type": "emoji", "value": "👋", "size": "lg" },
|
||||
"navigation": { "defaultNextScreenId": "question-1" }
|
||||
}
|
||||
```
|
||||
|
||||
Рантайм выводит заголовок по центру, кнопку «Next» (или `defaultTexts.nextButton`) и позволяет вернуться назад, если это разрешено в `header`. Логика описана в `InfoTemplate` и `buildLayoutQuestionProps` — дополнительные параметры (`font`, `color`, `align`) влияют на типографику.【F:src/components/funnel/templates/InfoTemplate.tsx†L1-L99】【F:src/lib/funnel/types.ts†L74-L131】
|
||||
|
||||
### Экран с вопросом и вариантами (`template: "list"`)
|
||||
|
||||
Базовый интерактивный экран. Поле `list` описывает варианты ответов:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "question-1",
|
||||
"template": "list",
|
||||
"title": { "text": "Какой формат подходит?" },
|
||||
"subtitle": { "text": "Можно выбрать несколько", "color": "muted" },
|
||||
"list": {
|
||||
"selectionType": "multi", // или "single"
|
||||
"options": [
|
||||
{ "id": "opt-online", "label": "Онлайн" },
|
||||
{ "id": "opt-offline", "label": "Офлайн", "description": "в вашем городе" }
|
||||
],
|
||||
"bottomActionButton": { "text": "Сохранить выбор" }
|
||||
},
|
||||
"bottomActionButton": { "show": false },
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "calendar",
|
||||
"rules": [
|
||||
{
|
||||
"nextScreenId": "coupon",
|
||||
"conditions": [{
|
||||
"screenId": "question-1",
|
||||
"operator": "includesAll",
|
||||
"optionIds": ["opt-online", "opt-offline"]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Особенности:
|
||||
|
||||
- `selectionType` определяет поведение: `single` строит радиокнопки, `multi` — чекбоксы. Компоненты `RadioAnswersList` и `SelectAnswersList` получают подготовленные данные из `mapListOptionsToButtons`.
|
||||
- Кнопка действия может описываться либо на уровне `list.bottomActionButton`, либо через общий `bottomActionButton`. В рантайме она скрывается, если `show: false`. Для списков с одиночным выбором и скрытой кнопкой включается автопереход на следующий экран при изменении ответа.【F:src/components/funnel/templates/ListTemplate.tsx†L1-L109】【F:src/components/funnel/FunnelRuntime.tsx†L73-L199】
|
||||
- Ответы сохраняются в массиве строк (идентификаторы опций) и используются навигацией и аналитикой.
|
||||
|
||||
### Экран выбора даты (`template: "date"`)
|
||||
|
||||
Предлагает три выпадающих списка (месяц, день, год) и опциональный блок с отформатированной датой.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "calendar",
|
||||
"template": "date",
|
||||
"title": { "text": "Когда планируете начать?" },
|
||||
"subtitle": { "text": "Выберите ориентировочную дату", "color": "muted" },
|
||||
"dateInput": {
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Вы выбрали"
|
||||
},
|
||||
"infoMessage": { "text": "Мы не будем делиться датой с третьими лицами." },
|
||||
"navigation": { "defaultNextScreenId": "contact" }
|
||||
}
|
||||
```
|
||||
|
||||
Особенности:
|
||||
|
||||
- Значение сохраняется как массив `[month, day, year]` внутри `answers` рантайма.
|
||||
- Кнопка «Next» активируется только после заполнения всех полей. Настройка текстов и подсказок — через объект `dateInput` (placeholder, label, формат для превью).
|
||||
- При `showSelectedDate: true` под кнопкой появляется подтверждающий блок с читабельной датой.【F:src/components/funnel/templates/DateTemplate.tsx†L1-L209】【F:src/lib/funnel/types.ts†L133-L189】
|
||||
|
||||
### Экран формы (`template: "form"`)
|
||||
|
||||
Подходит для сбора контактных данных. Поле `fields` содержит список текстовых инпутов со своими правилами.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "contact",
|
||||
"template": "form",
|
||||
"title": { "text": "Оставьте контакты" },
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "required": true, "maxLength": 60 },
|
||||
{
|
||||
"id": "email",
|
||||
"label": "E-mail",
|
||||
"type": "email",
|
||||
"validation": {
|
||||
"pattern": "^\\S+@\\S+\\.\\S+$",
|
||||
"message": "Введите корректный e-mail"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Неверный формат"
|
||||
},
|
||||
"navigation": { "defaultNextScreenId": "coupon" }
|
||||
}
|
||||
```
|
||||
|
||||
Особенности рантайма:
|
||||
|
||||
- Локальное состояние синхронизируется с глобальным через `onFormDataChange` — данные сериализуются в JSON-строку и хранятся в массиве ответов (первый элемент).【F:src/components/funnel/FunnelRuntime.tsx†L46-L118】
|
||||
- Кнопка продолжения (`defaultTexts.continueButton` или «Continue») активна, если все обязательные поля заполнены. Валидаторы проверяют `required`, `maxLength` и регулярное выражение из `validation.pattern` с кастомными сообщениями.【F:src/components/funnel/templates/FormTemplate.tsx†L1-L119】【F:src/lib/funnel/types.ts†L191-L238】
|
||||
|
||||
### Экран промокода (`template: "coupon"`)
|
||||
|
||||
Отображает купон с акцией и позволяет скопировать промокод.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": { "text": "Поздравляем!" },
|
||||
"subtitle": { "text": "Получите скидку" },
|
||||
"coupon": {
|
||||
"title": { "text": "Скидка 20%" },
|
||||
"offer": {
|
||||
"title": { "text": "-20% на первый заказ" },
|
||||
"description": { "text": "Действует до конца месяца" }
|
||||
},
|
||||
"promoCode": { "text": "START20" },
|
||||
"footer": { "text": "Скопируйте код и введите при оформлении" }
|
||||
},
|
||||
"copiedMessage": "Код {code} скопирован!",
|
||||
"navigation": { "defaultNextScreenId": "final-info" }
|
||||
}
|
||||
```
|
||||
|
||||
`CouponTemplate` копирует код в буфер обмена и показывает уведомление `copiedMessage` (строка с подстановкой `{code}`). Кнопка продолжения использует `defaultTexts.continueButton` или значение «Continue».【F:src/components/funnel/templates/CouponTemplate.tsx†L1-L111】【F:src/lib/funnel/types.ts†L191-L230】
|
||||
|
||||
## Конструктор (Builder)
|
||||
|
||||
Конструктор помогает собирать JSON-конфигурацию и состоит из трёх основных областей:
|
||||
|
||||
1. **Верхняя панель** (`BuilderTopBar`). Позволяет создать пустой проект, загрузить готовый JSON и экспортировать текущую конфигурацию. Импорт использует `deserializeFunnelDefinition`, добавляющий служебные координаты для канваса. Экспорт сериализует состояние обратно в формат `FunnelDefinition` (`serializeBuilderState`).【F:src/components/admin/builder/BuilderTopBar.tsx†L1-L79】【F:src/lib/admin/builder/utils.ts†L1-L58】
|
||||
2. **Канвас** (`BuilderCanvas`). Отображает экраны цепочкой, даёт возможность добавлять новые (`add-screen`), менять порядок drag-and-drop (`reorder-screens`) и выбирать экран для редактирования. Каждый экран показывает тип шаблона, количество опций и ссылку на следующий экран по умолчанию.【F:src/components/admin/builder/BuilderCanvas.tsx†L1-L132】
|
||||
3. **Боковая панель** (`BuilderSidebar`). Содержит две вкладки состояния:
|
||||
- Когда экран не выбран, показываются настройки воронки (ID, заголовок, описание, стартовый экран) и сводка валидации (`validateBuilderState`).【F:src/components/admin/builder/BuilderSidebar.tsx†L1-L188】【F:src/lib/admin/builder/validation.ts†L1-L168】
|
||||
- Для выбранного экрана доступны поля заголовков, параметры списка (тип выбора, опции), правила навигации, кастомизация кнопок и инструмент удаления. Все изменения отправляются через `update-screen`, `update-navigation` и вспомогательные обработчики, формируя корректный JSON.
|
||||
|
||||
### Предпросмотр
|
||||
|
||||
Компонент `BuilderPreview` визуализирует выбранный экран, используя те же шаблоны, что и боевой рантайм (`ListTemplate`, `InfoTemplate` и др.). Для симуляции действий используются заглушки — выбор опций, заполнение формы и навигация обновляют локальное состояние предпросмотра, но не меняют структуру воронки. При переключении экрана состояние сбрасывается, что позволяет увидеть дефолтное поведение каждого шаблона.【F:src/components/admin/builder/BuilderPreview.tsx†L1-L123】
|
||||
|
||||
### Валидация и сериализация
|
||||
|
||||
`validateBuilderState` проверяет уникальность идентификаторов экранов и опций, корректность ссылок в навигации и наличие переходов. Ошибки и предупреждения отображаются в боковой панели. При экспорте координаты канваса удаляются, чтобы JSON соответствовал ожиданиям рантайма. Ответы пользователей рантайм хранит в структуре `Record<string, string[]>`, где ключ — `id` экрана, а значение — массив выбранных значений (опций, компонентов даты или сериализованные данные формы).【F:src/lib/admin/builder/validation.ts†L1-L168】【F:src/lib/admin/builder/utils.ts†L1-L86】【F:src/components/funnel/FunnelRuntime.tsx†L1-L215】
|
||||
|
||||
## Рабочий процесс
|
||||
|
||||
1. Создайте экраны через верхнюю панель или кнопку на канвасе. Каждый новый экран получает уникальный ID (`screen-{n}`).
|
||||
2. Настройте порядок переходов drag-and-drop и установите `firstScreenId`, если стартовать нужно не с первого элемента.
|
||||
3. Заполните контент для каждого шаблона, настройте условия в `navigation.rules` и убедитесь, что `defaultNextScreenId` указан для веток без правил.
|
||||
4. Проверьте сводку валидации — при ошибках экспорт JSON будет возможен, но рантайм может не смочь построить маршрут.
|
||||
5. Экспортируйте JSON и передайте его рантайму (`<FunnelRuntime funnel={definition} initialScreenId={definition.meta.firstScreenId} />`).
|
||||
|
||||
Такой подход гарантирует, что конструктор и рантайм используют одну и ту же схему данных, а визуальные шаблоны ведут себя предсказуемо при изменении конфигурации.
|
||||
@ -7,6 +7,8 @@
|
||||
"build": "next build --turbopack",
|
||||
"start": "next start",
|
||||
"lint": "eslint",
|
||||
"bake:funnels": "node scripts/bake-funnels.mjs",
|
||||
"prebuild": "npm run bake:funnels",
|
||||
"storybook": "storybook dev -p 6006 --ci",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
|
||||
663
public/funnels/funnel-test-variants.json
Normal file
663
public/funnels/funnel-test-variants.json
Normal file
@ -0,0 +1,663 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "funnel-test",
|
||||
"title": "Relationship Portrait",
|
||||
"description": "Demo funnel mirroring design screens with branching by analysis target.",
|
||||
"firstScreenId": "intro-welcome"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Next",
|
||||
"continueButton": "Continue"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "intro-welcome",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Вы не одиноки в этом страхе",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Многие боятся повторить прошлый опыт. Мы поможем распознать верные сигналы и выбрать «своего» человека.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "default",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "❤️",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "intro-statistics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "intro-statistics",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "По нашей статистике 51 % женщин Овнов доверяются эмоциям. Но одной чувствительности мало. Мы покажем, какие качества второй половинки дадут тепло и уверенность, и изобразим её портрет.",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "🔥❤️",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "intro-partner-traits"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "intro-partner-traits",
|
||||
"template": "info",
|
||||
"header": {
|
||||
"showBackButton": false
|
||||
},
|
||||
"title": {
|
||||
"text": "Такой партнёр умеет слышать и поддерживать, а вы — человек с глубокой душой, который ценит искренность и силу настоящих чувств.",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "💖",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "birth-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "birth-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда ты родился?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "В момент вашего рождения заложенны глубинные закономерности.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "MM",
|
||||
"dayPlaceholder": "DD",
|
||||
"yearPlaceholder": "YYYY",
|
||||
"monthLabel": "Month",
|
||||
"dayLabel": "Day",
|
||||
"yearLabel": "Year",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Выбранная дата:"
|
||||
},
|
||||
"infoMessage": {
|
||||
"text": "Мы не передаем личную информацию, она остаётся в безопасности и под вашим контролем.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "address-form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "address-form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Which best represents your hair loss and goals?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Let's personalize your hair care journey",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"id": "address",
|
||||
"label": "Address",
|
||||
"placeholder": "Enter your full address",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"maxLength": 200
|
||||
}
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "${field} обязательно для заполнения",
|
||||
"maxLength": "Максимум ${maxLength} символов",
|
||||
"invalidFormat": "Неверный формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "statistics-text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "statistics-text",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Which best represents your hair loss and goals?",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "По нашей статистике 51 % женщин Овнов доверяются эмоциям. Но одной чувствительности мало. Мы покажем, какие качества второй половинки дадут тепло и уверенность, и изобразим её портрет.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "default",
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gender",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какого ты пола?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Все начинается с тебя! Выбери свой пол.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "female",
|
||||
"label": "FEMALE",
|
||||
"emoji": "💗"
|
||||
},
|
||||
{
|
||||
"id": "male",
|
||||
"label": "MALE",
|
||||
"emoji": "💙"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "relationship-status"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "relationship-status",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Вы сейчас?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Это нужно, чтобы портрет и советы были точнее.",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "in-relationship",
|
||||
"label": "В отношениях"
|
||||
},
|
||||
{
|
||||
"id": "single",
|
||||
"label": "Свободны"
|
||||
},
|
||||
{
|
||||
"id": "after-breakup",
|
||||
"label": "После расставания"
|
||||
},
|
||||
{
|
||||
"id": "complicated",
|
||||
"label": "Все сложно"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "analysis-target"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "analysis-target",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Кого анализируем?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "current-partner",
|
||||
"label": "Текущего партнера"
|
||||
},
|
||||
{
|
||||
"id": "crush",
|
||||
"label": "Человека, который нравится"
|
||||
},
|
||||
{
|
||||
"id": "ex-partner",
|
||||
"label": "Бывшего"
|
||||
},
|
||||
{
|
||||
"id": "future-partner",
|
||||
"label": "Будущую встречу"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "partner-age"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partner-age",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Возраст партнера",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Чтобы портрет был максимально точным, уточните возраст.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "under-29",
|
||||
"label": "До 29"
|
||||
},
|
||||
{
|
||||
"id": "30-39",
|
||||
"label": "30-39"
|
||||
},
|
||||
{
|
||||
"id": "40-49",
|
||||
"label": "40-49"
|
||||
},
|
||||
{
|
||||
"id": "50-59",
|
||||
"label": "50-59"
|
||||
},
|
||||
{
|
||||
"id": "60-plus",
|
||||
"label": "60+"
|
||||
}
|
||||
]
|
||||
},
|
||||
"variants": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": ["current-partner"]
|
||||
}
|
||||
],
|
||||
"overrides": {
|
||||
"title": {
|
||||
"text": "Возраст текущего партнера",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": ["crush"]
|
||||
}
|
||||
],
|
||||
"overrides": {
|
||||
"title": {
|
||||
"text": "Возраст человека, который нравится",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"show": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": ["ex-partner"]
|
||||
}
|
||||
],
|
||||
"overrides": {
|
||||
"title": {
|
||||
"text": "Возраст бывшего",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": ["future-partner"]
|
||||
}
|
||||
],
|
||||
"overrides": {
|
||||
"title": {
|
||||
"text": "Возраст будущего партнера",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Чтобы мы не упустили важные нюансы будущей встречи.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"navigation": {
|
||||
"rules": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "partner-age",
|
||||
"operator": "includesAny",
|
||||
"optionIds": ["under-29"]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "age-refine"
|
||||
}
|
||||
],
|
||||
"defaultNextScreenId": "partner-ethnicity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "age-refine",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Уточните чуть точнее",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Чтобы портрет был максимально похож.",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "18-21",
|
||||
"label": "18-21"
|
||||
},
|
||||
{
|
||||
"id": "22-25",
|
||||
"label": "22-25"
|
||||
},
|
||||
{
|
||||
"id": "26-29",
|
||||
"label": "26-29"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "partner-ethnicity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partner-ethnicity",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Этническая принадлежность твоей второй половинки?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "white",
|
||||
"label": "White"
|
||||
},
|
||||
{
|
||||
"id": "hispanic",
|
||||
"label": "Hispanic / Latino"
|
||||
},
|
||||
{
|
||||
"id": "african",
|
||||
"label": "African / African-American"
|
||||
},
|
||||
{
|
||||
"id": "asian",
|
||||
"label": "Asian"
|
||||
},
|
||||
{
|
||||
"id": "south-asian",
|
||||
"label": "Indian / South Asian"
|
||||
},
|
||||
{
|
||||
"id": "middle-eastern",
|
||||
"label": "Middle Eastern / Arab"
|
||||
},
|
||||
{
|
||||
"id": "indigenous",
|
||||
"label": "Native American / Indigenous"
|
||||
},
|
||||
{
|
||||
"id": "no-preference",
|
||||
"label": "No preference"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "partner-eyes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partner-eyes",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что из этого «про глаза»?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "warm-glow",
|
||||
"label": "Тёплые искры на свету"
|
||||
},
|
||||
{
|
||||
"id": "clear-depth",
|
||||
"label": "Прозрачная глубина"
|
||||
},
|
||||
{
|
||||
"id": "green-sheen",
|
||||
"label": "Зелёный отлив на границе зрачка"
|
||||
},
|
||||
{
|
||||
"id": "steel-glint",
|
||||
"label": "Холодный стальной отблеск"
|
||||
},
|
||||
{
|
||||
"id": "deep-shadow",
|
||||
"label": "Насыщенная темнота"
|
||||
},
|
||||
{
|
||||
"id": "dont-know",
|
||||
"label": "Не знаю"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "partner-hair-length"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partner-hair-length",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите длину волос",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "От неё зависит форма и настроение портрета.",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "short",
|
||||
"label": "Короткие"
|
||||
},
|
||||
{
|
||||
"id": "medium",
|
||||
"label": "Средние"
|
||||
},
|
||||
{
|
||||
"id": "long",
|
||||
"label": "Длинные"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "burnout-support"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "burnout-support",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Когда ты выгораешь, тебе нужно чтобы партнёр...",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{
|
||||
"id": "reassure",
|
||||
"label": "Признал ваше разочарование и успокоил"
|
||||
},
|
||||
{
|
||||
"id": "emotional-support",
|
||||
"label": "Дал эмоциональную опору и безопасное пространство"
|
||||
},
|
||||
{
|
||||
"id": "take-over",
|
||||
"label": "Перехватил быт/дела, чтобы вы восстановились"
|
||||
},
|
||||
{
|
||||
"id": "energize",
|
||||
"label": "Вдохнул энергию через цель и короткий план действий"
|
||||
},
|
||||
{
|
||||
"id": "switch-positive",
|
||||
"label": "Переключил на позитив: прогулка, кино, смешные истории"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Continue",
|
||||
"show": false
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "special-offer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "special-offer",
|
||||
"template": "coupon",
|
||||
"header": {
|
||||
"show": false
|
||||
},
|
||||
"title": {
|
||||
"text": "Тебе повезло!",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Ты получил специальную эксклюзивную скидку на 94%",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted",
|
||||
"align": "center"
|
||||
},
|
||||
"copiedMessage": "Промокод \"{code}\" скопирован!",
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "Special Offer",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "94% OFF",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"color": "card",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Одноразовая эксклюзивная скидка",
|
||||
"font": "inter",
|
||||
"weight": "semiBold",
|
||||
"color": "card"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "HAIR50",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Скопируйте или нажмите Continue",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted",
|
||||
"size": "sm"
|
||||
}
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Continue"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
313
public/funnels/ru-career-accelerator.json
Normal file
313
public/funnels/ru-career-accelerator.json
Normal file
@ -0,0 +1,313 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-career-accelerator",
|
||||
"title": "CareerUp: рывок в карьере",
|
||||
"description": "Воронка карьерного акселератора для специалистов и руководителей.",
|
||||
"firstScreenId": "welcome"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "welcome",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Повысь доход и статус за 12 недель",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Коуч, карьерный стратег и HR-директор ведут тебя к новой должности или росту дохода.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "🚀",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Пройти диагностику"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "pain"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pain",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Почему карьера застопорилась?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Нет стратегии, страх переговоров и слабый личный бренд. Мы закрываем каждый пробел.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "goal-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "goal-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда хочешь выйти на новую позицию?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Сформируем спринты под конкретный дедлайн.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Цель к:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "current-role"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "current-role",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Текущая роль",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "specialist", "label": "Специалист" },
|
||||
{ "id": "lead", "label": "Тимлид" },
|
||||
{ "id": "manager", "label": "Руководитель отдела" },
|
||||
{ "id": "c-level", "label": "C-level" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "target"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "target",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Желаемая цель",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "promotion", "label": "Повышение внутри компании" },
|
||||
{ "id": "newjob", "label": "Переход в топ-компанию" },
|
||||
{ "id": "salary", "label": "Рост дохода на 50%" },
|
||||
{ "id": "relocate", "label": "Релокация" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "case"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "case",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "История Марии: +85% к доходу",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "За 9 недель она прошла программу, обновила резюме, договорилась о relocation и заняла позицию руководителя продукта.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bottlenecks"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bottlenecks",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Где нужна поддержка?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "resume", "label": "Резюме и LinkedIn" },
|
||||
{ "id": "network", "label": "Нетворкинг" },
|
||||
{ "id": "interview", "label": "Интервью" },
|
||||
{ "id": "negotiation", "label": "Переговоры о зарплате" },
|
||||
{ "id": "leadership", "label": "Лидерские навыки" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "program-format"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "program-format",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой формат подходит?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "group", "label": "Групповой акселератор" },
|
||||
{ "id": "1on1", "label": "Индивидуальное сопровождение" },
|
||||
{ "id": "vip", "label": "Executive программа" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получить план роста",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "placeholder": "Как к вам обращаться", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Получить карьерный план", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "mentor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mentor",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Твой наставник",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Ex-HR Director из Microsoft поможет построить стратегию и проведёт ролевые интервью.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите пакет",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "start", "label": "Start — 6 недель" },
|
||||
{ "id": "pro", "label": "Pro — 12 недель" },
|
||||
{ "id": "elite", "label": "Elite — 16 недель + наставник" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Бонусы при оплате сегодня",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Шаблоны писем рекрутерам, библиотека резюме и доступ к закрытому карьерному клубу.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Зафиксируй скидку и бонусы",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка 20% и два дополнительных карьерных созвона.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "CareerUp",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-20%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Программа + 2 коуч-сессии",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "CAREER20",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы активировать предложение",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
314
public/funnels/ru-finance-freedom.json
Normal file
314
public/funnels/ru-finance-freedom.json
Normal file
@ -0,0 +1,314 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-finance-freedom",
|
||||
"title": "Capital Sense: финансовая свобода",
|
||||
"description": "Воронка для консультаций по инвестициям и личному финансовому планированию.",
|
||||
"firstScreenId": "intro"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "intro",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Сформируй капитал, который работает за тебя",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Персональный финансовый план, подбор инструментов и сопровождение на каждом шаге.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "💼",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Начать"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "fear"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fear",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Почему деньги не приносят свободу?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Разные цели, хаотичные инвестиции и страх потерять. Мы создаём стратегию с защитой и ростом.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "goal-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "goal-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда хочешь достичь финансовой цели?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Укажи дату, чтобы рассчитать необходимые шаги.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Цель к дате:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "current-income"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "current-income",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой у тебя ежемесячный доход?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "lt100k", "label": "До 100 000 ₽" },
|
||||
{ "id": "100-250", "label": "100 000 – 250 000 ₽" },
|
||||
{ "id": "250-500", "label": "250 000 – 500 000 ₽" },
|
||||
{ "id": "500plus", "label": "Свыше 500 000 ₽" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "savings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "savings",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Как распределяются накопления?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "deposit", "label": "Банковские вклады" },
|
||||
{ "id": "stocks", "label": "Акции и фонды" },
|
||||
{ "id": "realty", "label": "Недвижимость" },
|
||||
{ "id": "business", "label": "Собственный бизнес" },
|
||||
{ "id": "cash", "label": "Храню в наличных" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "risk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "risk",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Готовность к риску",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "conservative", "label": "Консервативная стратегия" },
|
||||
{ "id": "balanced", "label": "Сбалансированный портфель" },
|
||||
{ "id": "aggressive", "label": "Готов к высоким рискам ради роста" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "case"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "case",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "История Александра: капитал 12 млн за 5 лет",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Использовали облигации, дивидендные акции и страхование. Доходность 18% при низком риске.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "priorities"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "priorities",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выбери финансовые приоритеты",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "capital", "label": "Долгосрочный капитал" },
|
||||
{ "id": "passive", "label": "Пассивный доход" },
|
||||
{ "id": "education", "label": "Образование детей" },
|
||||
{ "id": "pension", "label": "Пенсия без тревог" },
|
||||
{ "id": "protection", "label": "Страхование и защита" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получить расчёт стратегии",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "placeholder": "Как вас зовут", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Получить PDF-план", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "advisor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "advisor",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Ваш персональный советник",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Сертифицированный финансовый консультант составит портфель и будет сопровождать на ежемесячных созвонах.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите пакет сопровождения",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "start", "label": "Start — до 2 млн ₽" },
|
||||
{ "id": "growth", "label": "Growth — до 10 млн ₽" },
|
||||
{ "id": "elite", "label": "Elite — от 10 млн ₽ и Family Office" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Бонусы к записи сегодня",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Инвестиционный чек-лист и бесплатный аудит страховок от партнёра.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Забронируйте условия",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка 25% на первый месяц сопровождения и аудит портфеля.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "Capital Sense",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-25%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Первый месяц и аудит портфеля",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "FIN25",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы активировать промокод",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
356
public/funnels/ru-fitness-transform.json
Normal file
356
public/funnels/ru-fitness-transform.json
Normal file
@ -0,0 +1,356 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-fitness-transform",
|
||||
"title": "Фитнес-вызов: Тело мечты за 12 недель",
|
||||
"description": "Воронка для продажи онлайн-программы персональных тренировок и питания.",
|
||||
"firstScreenId": "intro-hero"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "intro-hero",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Создай тело, которое будет восхищать",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Личный куратор, готовые тренировки и поддержка нутрициолога для стремительного результата.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "💪",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Начать диагностику"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "pain-check"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pain-check",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Почему результат не держится?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "92% наших клиентов приходят после десятков попыток похудеть. Мы устраняем коренные причины: гормональный фон, сон, питание.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "target-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "target-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда планируешь увидеть первые изменения?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Укажи желаемую дату — мы построим обратный план.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Целевая дата:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "current-state"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "current-state",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что больше всего мешает сейчас?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "time", "label": "Нет времени на зал" },
|
||||
{ "id": "food", "label": "Срывы в питании" },
|
||||
{ "id": "motivation", "label": "Не хватает мотивации" },
|
||||
{ "id": "health", "label": "Боли в спине/суставах" },
|
||||
{ "id": "plateau", "label": "Вес стоит на месте" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "goal-selection"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "goal-selection",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какая цель приоритетна?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Выбери один вариант — мы адаптируем программу.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "fat-loss", "label": "Снижение веса" },
|
||||
{ "id": "tone", "label": "Упругость и рельеф" },
|
||||
{ "id": "health", "label": "Самочувствие и энергия" },
|
||||
{ "id": "postpartum", "label": "Восстановление после родов" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "success-story"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "success-story",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Света минус 14 кг за 12 недель",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Она работала по 12 часов в офисе. Мы составили план из 30-минутных тренировок и настроили питание без голода. Теперь она ведёт блог и вдохновляет подруг.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "lifestyle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lifestyle",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Сколько времени готов(а) уделять?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "15min", "label": "15–20 минут в день" },
|
||||
{ "id": "30min", "label": "30–40 минут" },
|
||||
{ "id": "60min", "label": "60 минут и более" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "nutrition"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nutrition",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Питание без жёстких запретов",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Балансируем рацион под твои привычки: любимые блюда остаются, меняются только пропорции.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "support-format"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "support-format",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой формат поддержки комфортен?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "chat", "label": "Чат с куратором ежедневно" },
|
||||
{ "id": "calls", "label": "Созвоны раз в неделю" },
|
||||
{ "id": "video", "label": "Видеоразбор техники" },
|
||||
{ "id": "community", "label": "Группа единомышленников" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "contact-form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contact-form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Почти готово! Оставь контакты для персональной стратегии",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"id": "name",
|
||||
"label": "Имя",
|
||||
"placeholder": "Как к тебе обращаться",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"id": "phone",
|
||||
"label": "Телефон",
|
||||
"placeholder": "+7 (___) ___-__-__",
|
||||
"type": "tel",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "email",
|
||||
"label": "Email",
|
||||
"placeholder": "Для отправки материалов",
|
||||
"type": "email",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"maxLength": "Максимум ${maxLength} символов",
|
||||
"invalidFormat": "Проверь формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coach-match"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coach-match",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Подбираем наставника",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Мы нашли тренера, который специализируется на твоём запросе и будет на связи 24/7.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus-overview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus-overview",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Что входит в программу",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Получишь 36 адаптивных тренировок, 3 чек-листа питания, психологическую поддержку и доступ к закрытым эфиром.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "package-choice"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "package-choice",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выбери формат участия",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "online", "label": "Онлайн-куратор и видеоуроки" },
|
||||
{ "id": "vip", "label": "VIP: личные созвоны и чат 24/7" },
|
||||
{ "id": "studio", "label": "Комбо: онлайн + студийные тренировки" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "final-offer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "final-offer",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Зафиксируй место и подарок",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка действует 24 часа после прохождения диагностики.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "Фитнес-вызов",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-35%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Персональная программа и чат с тренером",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "BODY35",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажми \"Продолжить\" чтобы закрепить скидку",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
330
public/funnels/ru-interior-signature.json
Normal file
330
public/funnels/ru-interior-signature.json
Normal file
@ -0,0 +1,330 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-interior-signature",
|
||||
"title": "Design Bureau: интерьер под ключ",
|
||||
"description": "Воронка студии дизайна интерьера с авторским сопровождением ремонта.",
|
||||
"firstScreenId": "intro"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "intro",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Интерьер, который отражает ваш характер",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Создаём дизайн-проекты премиум-класса с полным контролем ремонта и экономией бюджета до 18%.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "🏡",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Начать проект"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "problem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "problem",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Типовая планировка крадёт эмоции",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Мы превращаем квадратные метры в пространство, где хочется жить, а не просто находиться.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "finish-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "finish-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда планируете переезд?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Укажи сроки, чтобы мы составили реалистичный план работ.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Переезд:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "property-type"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "property-type",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой объект оформляете?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "apartment", "label": "Квартира" },
|
||||
{ "id": "house", "label": "Дом" },
|
||||
{ "id": "office", "label": "Коммерческое пространство" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "style"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "style",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Стиль, который вдохновляет",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "minimal", "label": "Минимализм" },
|
||||
{ "id": "loft", "label": "Лофт" },
|
||||
{ "id": "classic", "label": "Современная классика" },
|
||||
{ "id": "eco", "label": "Эко" },
|
||||
{ "id": "mix", "label": "Эклектика" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "pain-points"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pain-points",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что вызывает наибольшие сложности?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "planning", "label": "Планировка" },
|
||||
{ "id": "contractors", "label": "Поиск подрядчиков" },
|
||||
{ "id": "budget", "label": "Контроль бюджета" },
|
||||
{ "id": "decor", "label": "Подбор мебели и декора" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "case"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "case",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Квартира в ЖК CITY PARK",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Мы оптимизировали планировку, сэкономили 2,4 млн ₽ на поставщиках и завершили ремонт на 3 недели раньше срока.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "services"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "services",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Что входит в нашу работу",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "3D-визуализации, рабочие чертежи, авторский надзор, логистика материалов и финансовый контроль.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "budget"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "budget",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Планируемый бюджет проекта",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "2m", "label": "до 2 млн ₽" },
|
||||
{ "id": "5m", "label": "2 – 5 млн ₽" },
|
||||
{ "id": "10m", "label": "5 – 10 млн ₽" },
|
||||
{ "id": "10mplus", "label": "Более 10 млн ₽" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получите концепцию и смету",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "placeholder": "Как к вам обращаться", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Получить презентацию", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "designer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "designer",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Персональный дизайнер",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Автор проектов для бизнес-элиты. Ведёт максимум 5 объектов, чтобы уделять максимум внимания.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите формат работы",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "concept", "label": "Concept — планировка и визуализации" },
|
||||
{ "id": "supervision", "label": "Control — авторский надзор" },
|
||||
{ "id": "turnkey", "label": "Turnkey — ремонт под ключ" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Бонусы при бронировании сегодня",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Авторский колорит и подбор мебели от итальянских брендов со скидкой до 30%.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Зафиксируйте привилегии",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка 20% на дизайн-проект и доступ к базе подрядчиков.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "Design Bureau",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-20%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Дизайн-проект + база подрядчиков",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "DESIGN20",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы получить предложение",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
311
public/funnels/ru-kids-robotics.json
Normal file
311
public/funnels/ru-kids-robotics.json
Normal file
@ -0,0 +1,311 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-kids-robotics",
|
||||
"title": "RoboKids: будущее ребёнка",
|
||||
"description": "Воронка для школы робототехники и программирования для детей 6-14 лет.",
|
||||
"firstScreenId": "welcome"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "welcome",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Подарите ребёнку навыки будущего",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Проектные занятия по робототехнике, программированию и soft skills в игровой форме.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "🤖",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Узнать программу"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "pain"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pain",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Почему важно развивать навыки сейчас",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "90% современных профессий требуют технического мышления. Мы даём ребёнку уверенность и любовь к обучению.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "birth-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "birth-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда родился ваш ребёнок?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Возраст помогает подобрать подходящую программу.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Возраст:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "interest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "interest",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что нравится ребёнку?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "lego", "label": "Конструировать" },
|
||||
{ "id": "games", "label": "Компьютерные игры" },
|
||||
{ "id": "science", "label": "Экспериментировать" },
|
||||
{ "id": "art", "label": "Рисовать и создавать истории" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "skills"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "skills",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какие навыки хотите усилить?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "logic", "label": "Логика и математика" },
|
||||
{ "id": "team", "label": "Командная работа" },
|
||||
{ "id": "presentation", "label": "Презентация проектов" },
|
||||
{ "id": "creativity", "label": "Креативность" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "case"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "case",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Кейс семьи Еремовых",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Сын собрал робота-доставщика и выиграл региональный конкурс. Теперь учится в технопарке.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "format"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "format",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой формат занятий удобен?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "offline", "label": "Очно в технопарке" },
|
||||
{ "id": "online", "label": "Онлайн-лаборатория" },
|
||||
{ "id": "hybrid", "label": "Комбо: онлайн + офлайн" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "schedule"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "schedule",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите расписание",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "weekend", "label": "Выходные" },
|
||||
{ "id": "weekday", "label": "Будни после школы" },
|
||||
{ "id": "intensive", "label": "Интенсивные каникулы" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получите бесплатный пробный урок",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "parentName", "label": "Имя родителя", "placeholder": "Как к вам обращаться", "type": "text", "required": true },
|
||||
{ "id": "childName", "label": "Имя ребёнка", "placeholder": "Имя ребёнка", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте корректность"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "mentor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mentor",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Ваш наставник",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Педагог MIT и финалист World Robot Olympiad проведёт вводную встречу и вовлечёт ребёнка в проект.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите программу",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "start", "label": "Start — 2 месяца" },
|
||||
{ "id": "pro", "label": "Pro — 6 месяцев" },
|
||||
{ "id": "elite", "label": "Elite — 12 месяцев + наставник" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Бонусы для новых семей",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Сертификат на 3D-печать проекта и доступ к киберспортивной студии.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Забронируйте место",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка 15% и подарок на первый месяц обучения.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "RoboKids",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-15%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Первый месяц + подарок",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "ROBO15",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы активировать скидку",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
330
public/funnels/ru-language-immersion.json
Normal file
330
public/funnels/ru-language-immersion.json
Normal file
@ -0,0 +1,330 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-language-immersion",
|
||||
"title": "LinguaPro: английский за 3 месяца",
|
||||
"description": "Воронка онлайн-школы английского языка для взрослых.",
|
||||
"firstScreenId": "start"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "start",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Говори уверенно через 12 недель",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Живые уроки с преподавателем, ежедневная практика и контроль прогресса.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "🌍",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Диагностика уровня"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "pain"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pain",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Почему 4 из 5 студентов не доходят до результата?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Нерегулярность, отсутствие практики и скучные уроки. Мы исправили каждую точку.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "goal-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "goal-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда предстоит важное событие на английском?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Сформируем план подготовки под конкретную дату.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Событие:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "current-level"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "current-level",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Оцени свой текущий уровень",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "starter", "label": "Начинаю с нуля" },
|
||||
{ "id": "elementary", "label": "Могу поддержать простую беседу" },
|
||||
{ "id": "intermediate", "label": "Хочу говорить свободно" },
|
||||
{ "id": "advanced", "label": "Нужен профессиональный английский" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "difficulties"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "difficulties",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что даётся сложнее всего?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "speaking", "label": "Разговорная речь" },
|
||||
{ "id": "listening", "label": "Понимание на слух" },
|
||||
{ "id": "grammar", "label": "Грамматика" },
|
||||
{ "id": "vocabulary", "label": "Словарный запас" },
|
||||
{ "id": "confidence", "label": "Стеснение" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "success-story"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "success-story",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Кейс Максима: оффер в международной компании",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "За 10 недель он прокачал разговорный до Upper-Intermediate, прошёл интервью и удвоил доход.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "study-format"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "study-format",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Как удобнее заниматься?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "individual", "label": "Индивидуально с преподавателем" },
|
||||
{ "id": "mini-group", "label": "Мини-группа до 4 человек" },
|
||||
{ "id": "intensive", "label": "Интенсив по выходным" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "practice"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "practice",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Практика каждый день",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Марафоны спикинга, разговорные клубы с носителями и тренажёр произношения в приложении.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "support"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "support",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что важно в поддержке?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "mentor", "label": "Личный куратор" },
|
||||
{ "id": "feedback", "label": "Еженедельный фидбек" },
|
||||
{ "id": "chat", "label": "Чат 24/7" },
|
||||
{ "id": "reports", "label": "Отчёт о прогрессе" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "contact-form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contact-form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получите индивидуальный учебный план",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "placeholder": "Как к вам обращаться", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Получите PDF-план", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте ввод"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "mentor-match"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mentor-match",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Мы подобрали вам преподавателя",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Сертифицированный CELTA преподаватель с опытом подготовки к собеседованиям.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "programs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "programs",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите программу",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "starter", "label": "Start Now — 8 недель" },
|
||||
{ "id": "pro", "label": "Career Boost — 12 недель" },
|
||||
{ "id": "vip", "label": "Executive — 16 недель + коуч" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Бонусы для тех, кто оплачивает сегодня",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Доступ к библиотеке TED-тренажёров и разговорный клуб в подарок.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Закрепите скидку",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка 30% на первый модуль и бонусный урок с носителем.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "LinguaPro",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-30%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Курс и разговорный клуб",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "LINGUA30",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы использовать промокод",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
313
public/funnels/ru-mind-balance.json
Normal file
313
public/funnels/ru-mind-balance.json
Normal file
@ -0,0 +1,313 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-mind-balance",
|
||||
"title": "MindBalance: психотерапия для результата",
|
||||
"description": "Воронка сервиса подбора психолога с поддержкой и пакетами сопровождения.",
|
||||
"firstScreenId": "welcome"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "welcome",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Верни устойчивость за 8 недель",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Персональный подбор терапевта, структурные сессии и поддержка между встречами.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "🧠",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Пройти тест"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "pain"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pain",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Ты не обязан справляться в одиночку",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Выгорание, тревога, сложности в отношениях — наши клиенты чувствовали то же. Сейчас живут без этого тяжёлого груза.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "stress-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stress-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда ты последний раз отдыхал(а) без тревог?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Это помогает оценить уровень стресса и подобрать ритм терапии.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Дата отдыха:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "state"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "state",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что чувствуешь чаще всего?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "anxiety", "label": "Тревога" },
|
||||
{ "id": "apathy", "label": "Апатия" },
|
||||
{ "id": "anger", "label": "Раздражительность" },
|
||||
{ "id": "insomnia", "label": "Проблемы со сном" },
|
||||
{ "id": "relationships", "label": "Конфликты" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "goals"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "goals",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "К чему хочешь прийти?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "energy", "label": "Больше энергии" },
|
||||
{ "id": "confidence", "label": "Уверенность в решениях" },
|
||||
{ "id": "relations", "label": "Гармония в отношениях" },
|
||||
{ "id": "selfcare", "label": "Ценность себя" },
|
||||
{ "id": "career", "label": "Сфокусированность в работе" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "success"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "success",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "История Ани: спокойствие вместо паники",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Через 7 сессий она перестала просыпаться ночью, получила повышение и наладила отношения с мужем.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "format"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "format",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой формат терапии удобен?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "online", "label": "Онлайн-видеосессии" },
|
||||
{ "id": "audio", "label": "Аудио и чат-поддержка" },
|
||||
{ "id": "offline", "label": "Офлайн в кабинете" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "frequency"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "frequency",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "С какой частотой готовы встречаться?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "weekly", "label": "Раз в неделю" },
|
||||
{ "id": "twice", "label": "Дважды в неделю" },
|
||||
{ "id": "flex", "label": "Гибкий график" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получить подбор психолога",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "placeholder": "Ваше имя", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Для плана терапии", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте ввод"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "therapist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "therapist",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Мы нашли специалиста",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Психолог с 9-летним опытом CBT, работает с тревогой и выгоранием. Первичная консультация — завтра.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите пакет",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "start", "label": "Start — 4 сессии" },
|
||||
{ "id": "focus", "label": "Focus — 8 сессий + чат" },
|
||||
{ "id": "deep", "label": "Deep — 12 сессий + коуч" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Подарок к старту",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Медитации MindBalance и ежедневный трекер настроения бесплатно.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Закрепите скидку",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка 20% на первый пакет и бонусный аудио-курс.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "MindBalance",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-20%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Первый пакет + аудио-курс",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "MIND20",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы применить промокод",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
314
public/funnels/ru-skin-renewal.json
Normal file
314
public/funnels/ru-skin-renewal.json
Normal file
@ -0,0 +1,314 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-skin-renewal",
|
||||
"title": "Glow Clinic: омоложение без боли",
|
||||
"description": "Воронка для клиники косметологии с диагностикой кожи и продажей курса процедур.",
|
||||
"firstScreenId": "welcome"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "welcome",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Верни коже сияние за 28 дней",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Лицо свежее, овал подтянутый, поры незаметны — результат подтверждён 418 клиентками.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "✨",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Пройти диагностику"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "problem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "problem",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "85% женщин старят три фактора",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Обезвоженность, пигментация и потеря тонуса. Находим источник и устраняем его комплексно.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "skin-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "skin-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда была последняя профессиональная чистка?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Дата поможет подобрать интенсивность и глубину процедур.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Последний визит:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "skin-type"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "skin-type",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой у тебя тип кожи?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "dry", "label": "Сухая" },
|
||||
{ "id": "combination", "label": "Комбинированная" },
|
||||
{ "id": "oily", "label": "Жирная" },
|
||||
{ "id": "sensitive", "label": "Чувствительная" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "primary-concern"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "primary-concern",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что беспокоит больше всего?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "wrinkles", "label": "Морщины" },
|
||||
{ "id": "pigmentation", "label": "Пигментация" },
|
||||
{ "id": "pores", "label": "Расширенные поры" },
|
||||
{ "id": "acne", "label": "Воспаления" },
|
||||
{ "id": "dryness", "label": "Сухость и шелушение" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "success"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "success",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "История Нади: минус 7 лет визуально",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Через 3 сеанса HydraGlow кожа стала плотной, контур подтянулся, ушла желтизна. Её фото попало в наш кейсбук.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "home-care"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "home-care",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Как ухаживаешь дома?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "basic", "label": "Только базовый уход" },
|
||||
{ "id": "active", "label": "Активные сыворотки" },
|
||||
{ "id": "spapro", "label": "Домашние аппараты" },
|
||||
{ "id": "none", "label": "Практически не ухаживаю" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "allergy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "allergy",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Есть ли ограничения?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "pregnancy", "label": "Беременность/ГВ" },
|
||||
{ "id": "allergy", "label": "Аллергия на кислоты" },
|
||||
{ "id": "derm", "label": "Дерматологические заболевания" },
|
||||
{ "id": "no", "label": "Нет ограничений" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "diagnostic-form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "diagnostic-form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получить персональный план ухода",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "placeholder": "Как к вам обращаться", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Получите чек-лист ухода", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "expert"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "expert",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Ваш персональный эксперт",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Врач-косметолог с 12-летним опытом проведёт диагностику, составит план процедур и будет на связи между визитами.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "plan-options"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plan-options",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите программу",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "express", "label": "Express Glow — 2 визита" },
|
||||
{ "id": "course", "label": "Total Lift — 4 визита" },
|
||||
{ "id": "vip", "label": "VIP Anti-Age — 6 визитов" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Подарок к записи сегодня",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Профессиональная сыворотка Medik8 и массаж шеи в подарок на первом приёме.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Забронируй курс со скидкой",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Только сегодня — до 40% на программу и подарок.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "Glow Clinic",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-40%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Курс омоложения + сыворотка",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "GLOW40",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы закрепить предложение",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
315
public/funnels/ru-travel-signature.json
Normal file
315
public/funnels/ru-travel-signature.json
Normal file
@ -0,0 +1,315 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-travel-signature",
|
||||
"title": "Signature Trips: путешествие мечты",
|
||||
"description": "Воронка для премиального турагентства по созданию индивидуальных путешествий.",
|
||||
"firstScreenId": "hero"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "hero",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Создадим путешествие, о котором будут говорить",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Личный тревел-архитектор, закрытые локации и полный сервис 24/7.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "✈️",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Начать проект"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "inspiration"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "inspiration",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Премиальный отдых начинается с мечты",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Мы создаём маршруты для Forbes, топ-менеджеров и семей, которые ценят приватность.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "travel-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "travel-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда планируете отправиться?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Дата позволяет нам зарезервировать лучшие отели и гидов заранее.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Старт путешествия:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "companions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "companions",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "С кем летите?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "solo", "label": "Соло" },
|
||||
{ "id": "couple", "label": "Пара" },
|
||||
{ "id": "family", "label": "Семья" },
|
||||
{ "id": "friends", "label": "Компания друзей" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "style"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "style",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой стиль отдыха хотите?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "beach", "label": "Пляжный релакс" },
|
||||
{ "id": "city", "label": "Городской lifestyle" },
|
||||
{ "id": "adventure", "label": "Приключения" },
|
||||
{ "id": "culture", "label": "Культура и гастрономия" },
|
||||
{ "id": "wellness", "label": "Wellness & spa" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "case"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "case",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Кейс семьи Морозовых",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "10 дней на Бали: вилла на скале, частный шеф, экскурсии на вертолёте. Экономия времени — 60 часов.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "wishlist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wishlist",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что должно быть обязательно?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "private", "label": "Приватные перелёты" },
|
||||
{ "id": "events", "label": "Закрытые мероприятия" },
|
||||
{ "id": "photographer", "label": "Личный фотограф" },
|
||||
{ "id": "kids", "label": "Детский клуб" },
|
||||
{ "id": "chef", "label": "Шеф-повар" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "budget"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "budget",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой бюджет готовы инвестировать?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "5k", "label": "до $5 000" },
|
||||
{ "id": "10k", "label": "$5 000 – $10 000" },
|
||||
{ "id": "20k", "label": "$10 000 – $20 000" },
|
||||
{ "id": "20kplus", "label": "Более $20 000" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получите концепт путешествия",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "placeholder": "Как к вам обращаться", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Получить концепт", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "concierge"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "concierge",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Ваш персональный консьерж",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Будет на связи 24/7, бронирует рестораны, решает любые вопросы во время поездки.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите формат сервиса",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "concept", "label": "Concept — разработка маршрута" },
|
||||
{ "id": "full", "label": "Full Care — сопровождение 24/7" },
|
||||
{ "id": "ultra", "label": "Ultra Lux — частный самолёт и охрана" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Специальный бонус",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "При бронировании сегодня — апгрейд номера и приватная фотосессия.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Забронируйте бонус",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Подарочный апгрейд и персональный гид входят в промо",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "Signature Trips",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "Premium Bonus",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "3xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Апгрейд номера + личный гид",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "SIGNATURE",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы зафиксировать бонус",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
315
public/funnels/ru-wedding-dream.json
Normal file
315
public/funnels/ru-wedding-dream.json
Normal file
@ -0,0 +1,315 @@
|
||||
{
|
||||
"meta": {
|
||||
"id": "ru-wedding-dream",
|
||||
"title": "DreamDay: свадьба без стресса",
|
||||
"description": "Воронка агентства свадебного продюсирования.",
|
||||
"firstScreenId": "welcome"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Далее",
|
||||
"continueButton": "Продолжить"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "welcome",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Создадим свадьбу, о которой мечтаете",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Команда продюсеров возьмёт на себя всё: от концепции до финального танца.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "💍",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Начать план"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "vision"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "vision",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Каждая история любви уникальна",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Мы создаём сценарии, которые отражают вашу пару, а не Pinterest-копию.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "На какую дату планируется свадьба?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Мы проверим занятость площадок и команд.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "ММ",
|
||||
"dayPlaceholder": "ДД",
|
||||
"yearPlaceholder": "ГГГГ",
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Дата свадьбы:"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "guests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "guests",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Сколько гостей ожидаете?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "small", "label": "До 30 гостей" },
|
||||
{ "id": "medium", "label": "30-70 гостей" },
|
||||
{ "id": "large", "label": "70-120 гостей" },
|
||||
{ "id": "xl", "label": "Более 120 гостей" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "style"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "style",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Опишите стиль праздника",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "classic", "label": "Классика" },
|
||||
{ "id": "modern", "label": "Современный шик" },
|
||||
{ "id": "boho", "label": "Бохо" },
|
||||
{ "id": "destination", "label": "Destination wedding" },
|
||||
{ "id": "party", "label": "Ночной клуб" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "case"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "case",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Свадьба Кати и Максима",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Горная Швейцария, закрытая вилла и живой оркестр. Сэкономили 18 часов подготовки еженедельно.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "priorities"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "priorities",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что важнее всего?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{ "id": "venue", "label": "Локация мечты" },
|
||||
{ "id": "show", "label": "Вау-программа" },
|
||||
{ "id": "decor", "label": "Дизайн и флористика" },
|
||||
{ "id": "photo", "label": "Фото и видео" },
|
||||
{ "id": "care", "label": "Отсутствие стресса" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "budget"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "budget",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какой бюджет планируете?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "3m", "label": "До 3 млн ₽" },
|
||||
{ "id": "5m", "label": "3-5 млн ₽" },
|
||||
{ "id": "8m", "label": "5-8 млн ₽" },
|
||||
{ "id": "8mplus", "label": "Более 8 млн ₽" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Получите концепцию свадьбы",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"fields": [
|
||||
{ "id": "names", "label": "Имена пары", "placeholder": "Имена", "type": "text", "required": true },
|
||||
{ "id": "phone", "label": "Телефон", "placeholder": "+7 (___) ___-__-__", "type": "tel", "required": true },
|
||||
{ "id": "email", "label": "Email", "placeholder": "Получить презентацию", "type": "email", "required": true }
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Проверьте формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "team"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "team",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Команда под вашу историю",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Продюсер, стилист, режиссёр и координатор. Каждую неделю — отчёт и контроль бюджета.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите формат сопровождения",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{ "id": "concept", "label": "Concept — идея и сценарий" },
|
||||
{ "id": "production", "label": "Production — организация под ключ" },
|
||||
{ "id": "lux", "label": "Luxury — destination + премиум команда" }
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "bonus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bonus",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Бонусы при бронировании сегодня",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"description": {
|
||||
"text": "Пробная встреча с ведущим и авторские клятвы, подготовленные нашим спичрайтером.",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": {
|
||||
"text": "Зафиксируйте дату и бонус",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Скидка 15% на продюсирование и бесплатная love-story съёмка.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"align": "center",
|
||||
"color": "muted"
|
||||
},
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "DreamDay",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "-15%",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Продюсирование + love-story",
|
||||
"font": "inter",
|
||||
"weight": "medium"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "DREAM15",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Нажмите, чтобы закрепить предложение",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"size": "sm",
|
||||
"color": "muted"
|
||||
}
|
||||
},
|
||||
"copiedMessage": "Промокод {code} скопирован!"
|
||||
}
|
||||
]
|
||||
}
|
||||
79
scripts/bake-funnels.mjs
Normal file
79
scripts/bake-funnels.mjs
Normal file
@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env node
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const projectRoot = path.resolve(__dirname, "..");
|
||||
const funnelsDir = path.join(projectRoot, "public", "funnels");
|
||||
const outputFile = path.join(projectRoot, "src", "lib", "funnel", "bakedFunnels.ts");
|
||||
|
||||
function formatFunnelRecord(funnels) {
|
||||
const entries = Object.entries(funnels)
|
||||
.map(([funnelId, definition]) => {
|
||||
const serialized = JSON.stringify(definition, null, 2);
|
||||
const indented = serialized
|
||||
.split("\n")
|
||||
.map((line, index) => (index === 0 ? line : ` ${line}`))
|
||||
.join("\n");
|
||||
return ` "${funnelId}": ${indented}`;
|
||||
})
|
||||
.join(",\n\n");
|
||||
|
||||
return `{
|
||||
${entries}\n}`;
|
||||
}
|
||||
|
||||
async function bakeFunnels() {
|
||||
const dirExists = await fs
|
||||
.access(funnelsDir)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!dirExists) {
|
||||
throw new Error(`Funnels directory not found: ${funnelsDir}`);
|
||||
}
|
||||
|
||||
const files = (await fs.readdir(funnelsDir)).sort((a, b) => a.localeCompare(b));
|
||||
const funnels = {};
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith(".json")) continue;
|
||||
|
||||
const filePath = path.join(funnelsDir, file);
|
||||
const raw = await fs.readFile(filePath, "utf8");
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse ${file}: ${error.message}`);
|
||||
}
|
||||
|
||||
const funnelId = parsed?.meta?.id ?? parsed?.id ?? file.replace(/\.json$/, "");
|
||||
|
||||
if (!funnelId || typeof funnelId !== "string") {
|
||||
throw new Error(
|
||||
`Unable to determine funnel id for '${file}'. Ensure the file contains an 'id' or 'meta.id' field.`
|
||||
);
|
||||
}
|
||||
|
||||
funnels[funnelId] = parsed;
|
||||
}
|
||||
|
||||
const headerComment = `/**\n * This file is auto-generated by scripts/bake-funnels.mjs.\n * Do not edit this file manually; update the source JSON files instead.\n */`;
|
||||
|
||||
const recordLiteral = formatFunnelRecord(funnels);
|
||||
const contents = `${headerComment}\n\nimport type { FunnelDefinition } from "./types";\n\nexport const BAKED_FUNNELS: Record<string, FunnelDefinition> = ${recordLiteral};\n`;
|
||||
|
||||
await fs.mkdir(path.dirname(outputFile), { recursive: true });
|
||||
await fs.writeFile(outputFile, contents, "utf8");
|
||||
|
||||
console.log(`Baked ${Object.keys(funnels).length} funnel(s) into ${path.relative(projectRoot, outputFile)}`);
|
||||
}
|
||||
|
||||
bakeFunnels().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@ -1,7 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { loadFunnelDefinition } from "@/lib/funnel/loadFunnelDefinition";
|
||||
import {
|
||||
listBakedFunnelScreenParams,
|
||||
peekBakedFunnelDefinition,
|
||||
loadFunnelDefinition,
|
||||
} from "@/lib/funnel/loadFunnelDefinition";
|
||||
import { FunnelRuntime } from "@/components/funnel/FunnelRuntime";
|
||||
|
||||
interface FunnelScreenPageProps {
|
||||
@ -11,11 +15,23 @@ interface FunnelScreenPageProps {
|
||||
}>;
|
||||
}
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return listBakedFunnelScreenParams();
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: FunnelScreenPageProps): Promise<Metadata> {
|
||||
const { funnelId } = await params;
|
||||
const funnel = await loadFunnelDefinition(funnelId);
|
||||
let funnel: ReturnType<typeof peekBakedFunnelDefinition>;
|
||||
try {
|
||||
funnel = peekBakedFunnelDefinition(funnelId);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load funnel '${funnelId}' for metadata:`, error);
|
||||
notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
title: funnel.meta.title ?? "Funnel",
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
import { loadFunnelDefinition } from "@/lib/funnel/loadFunnelDefinition";
|
||||
import {
|
||||
listBakedFunnelIds,
|
||||
peekBakedFunnelDefinition,
|
||||
} from "@/lib/funnel/loadFunnelDefinition";
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return listBakedFunnelIds().map((funnelId) => ({ funnelId }));
|
||||
}
|
||||
|
||||
interface FunnelRootPageProps {
|
||||
params: Promise<{
|
||||
@ -11,9 +20,9 @@ interface FunnelRootPageProps {
|
||||
export default async function FunnelRootPage({ params }: FunnelRootPageProps) {
|
||||
const { funnelId } = await params;
|
||||
|
||||
let funnel;
|
||||
let funnel: ReturnType<typeof peekBakedFunnelDefinition>;
|
||||
try {
|
||||
funnel = await loadFunnelDefinition(funnelId);
|
||||
funnel = peekBakedFunnelDefinition(funnelId);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load funnel '${funnelId}':`, error);
|
||||
notFound();
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { ArrowDown, ArrowRight, CircleSlash2, GitBranch } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useBuilderDispatch, useBuilderState } from "@/lib/admin/builder/context";
|
||||
import type {
|
||||
@ -359,7 +357,6 @@ export function BuilderCanvas() {
|
||||
const isDropAfter = dropIndex === screens.length && index === screens.length - 1;
|
||||
const rules = screen.navigation?.rules ?? [];
|
||||
const defaultNext = screen.navigation?.defaultNextScreenId;
|
||||
|
||||
const isLast = index === screens.length - 1;
|
||||
const defaultTargetIndex = defaultNext
|
||||
? screens.findIndex((candidate) => candidate.id === defaultNext)
|
||||
@ -409,7 +406,7 @@ export function BuilderCanvas() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{screen.subtitle?.text && (
|
||||
{("subtitle" in screen && screen.subtitle?.text) && (
|
||||
<p className="mt-3 max-h-12 overflow-hidden text-sm leading-snug text-muted-foreground">
|
||||
{screen.subtitle.text}
|
||||
</p>
|
||||
|
||||
@ -10,6 +10,7 @@ import { DateTemplate } from "@/components/funnel/templates/DateTemplate";
|
||||
import { CouponTemplate } from "@/components/funnel/templates/CouponTemplate";
|
||||
import { FormTemplate } from "@/components/funnel/templates/FormTemplate";
|
||||
import { resolveNextScreenId } from "@/lib/funnel/navigation";
|
||||
import { resolveScreenVariant } from "@/lib/funnel/variants";
|
||||
import { useFunnelRuntime } from "@/lib/funnel/FunnelProvider";
|
||||
import type {
|
||||
FunnelDefinition,
|
||||
@ -31,10 +32,11 @@ function estimatePathLength(funnel: FunnelDefinition, answers: FunnelAnswers): n
|
||||
while (currentScreenId && !visited.has(currentScreenId)) {
|
||||
visited.add(currentScreenId);
|
||||
|
||||
const currentScreen = funnel.screens.find(s => s.id === currentScreenId);
|
||||
const currentScreen = funnel.screens.find((s) => s.id === currentScreenId);
|
||||
if (!currentScreen) break;
|
||||
|
||||
const nextScreenId = resolveNextScreenId(currentScreen, answers, funnel.screens);
|
||||
|
||||
const resolvedScreen = resolveScreenVariant(currentScreen, answers);
|
||||
const nextScreenId = resolveNextScreenId(resolvedScreen, answers, funnel.screens);
|
||||
|
||||
// Если достигли конца или зацикливание
|
||||
if (!nextScreenId || visited.has(nextScreenId)) {
|
||||
@ -229,10 +231,18 @@ export function FunnelRuntime({ funnel, initialScreenId }: FunnelRuntimeProps) {
|
||||
funnel.meta.id
|
||||
);
|
||||
|
||||
const currentScreen = useMemo(() => {
|
||||
return getScreenById(funnel, initialScreenId) ?? funnel.screens[0];
|
||||
const baseScreen = useMemo(() => {
|
||||
const screen = getScreenById(funnel, initialScreenId) ?? funnel.screens[0];
|
||||
if (!screen) {
|
||||
throw new Error("Funnel definition does not contain any screens");
|
||||
}
|
||||
return screen;
|
||||
}, [funnel, initialScreenId]);
|
||||
|
||||
const currentScreen = useMemo(() => {
|
||||
return resolveScreenVariant(baseScreen, answers);
|
||||
}, [baseScreen, answers]);
|
||||
|
||||
const selectedOptionIds = answers[currentScreen.id] ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
799
src/lib/funnel/bakedFunnels.ts
Normal file
799
src/lib/funnel/bakedFunnels.ts
Normal file
@ -0,0 +1,799 @@
|
||||
/**
|
||||
* This file is auto-generated by scripts/bake-funnels.mjs.
|
||||
* Do not edit this file manually; update the source JSON files instead.
|
||||
*/
|
||||
|
||||
import type { FunnelDefinition } from "./types";
|
||||
|
||||
export const BAKED_FUNNELS: Record<string, FunnelDefinition> = {
|
||||
"funnel-test": {
|
||||
"meta": {
|
||||
"id": "funnel-test",
|
||||
"title": "Relationship Portrait",
|
||||
"description": "Demo funnel mirroring design screens with branching by analysis target.",
|
||||
"firstScreenId": "intro-welcome"
|
||||
},
|
||||
"defaultTexts": {
|
||||
"nextButton": "Next",
|
||||
"continueButton": "Continue"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "intro-welcome",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Вы не одиноки в этом страхе",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "Многие боятся повторить прошлый опыт. Мы поможем распознать верные сигналы и выбрать «своего» человека.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "default",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "❤️",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "intro-statistics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "intro-statistics",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "По нашей статистике 51 % женщин Овнов доверяются эмоциям. Но одной чувствительности мало. Мы покажем, какие качества второй половинки дадут тепло и уверенность, и изобразим её портрет.",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "🔥❤️",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "intro-partner-traits"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "intro-partner-traits",
|
||||
"template": "info",
|
||||
"header": {
|
||||
"showBackButton": false
|
||||
},
|
||||
"title": {
|
||||
"text": "Такой партнёр умеет слышать и поддерживать, а вы — человек с глубокой душой, который ценит искренность и силу настоящих чувств.",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"icon": {
|
||||
"type": "emoji",
|
||||
"value": "💖",
|
||||
"size": "xl"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "birth-date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "birth-date",
|
||||
"template": "date",
|
||||
"title": {
|
||||
"text": "Когда ты родился?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "В момент вашего рождения заложенны глубинные закономерности.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"dateInput": {
|
||||
"monthPlaceholder": "MM",
|
||||
"dayPlaceholder": "DD",
|
||||
"yearPlaceholder": "YYYY",
|
||||
"monthLabel": "Month",
|
||||
"dayLabel": "Day",
|
||||
"yearLabel": "Year",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Выбранная дата:"
|
||||
},
|
||||
"infoMessage": {
|
||||
"text": "Мы не передаем личную информацию, она остаётся в безопасности и под вашим контролем.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Next"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "address-form"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "address-form",
|
||||
"template": "form",
|
||||
"title": {
|
||||
"text": "Which best represents your hair loss and goals?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Let's personalize your hair care journey",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"id": "address",
|
||||
"label": "Address",
|
||||
"placeholder": "Enter your full address",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"maxLength": 200
|
||||
}
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "${field} обязательно для заполнения",
|
||||
"maxLength": "Максимум ${maxLength} символов",
|
||||
"invalidFormat": "Неверный формат"
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "statistics-text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "statistics-text",
|
||||
"template": "info",
|
||||
"title": {
|
||||
"text": "Which best represents your hair loss and goals?",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"description": {
|
||||
"text": "По нашей статистике 51 % женщин Овнов доверяются эмоциям. Но одной чувствительности мало. Мы покажем, какие качества второй половинки дадут тепло и уверенность, и изобразим её портрет.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "default",
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gender",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Какого ты пола?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Все начинается с тебя! Выбери свой пол.",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "female",
|
||||
"label": "FEMALE",
|
||||
"emoji": "💗"
|
||||
},
|
||||
{
|
||||
"id": "male",
|
||||
"label": "MALE",
|
||||
"emoji": "💙"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "relationship-status"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "relationship-status",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Вы сейчас?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Это нужно, чтобы портрет и советы были точнее.",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "in-relationship",
|
||||
"label": "В отношениях"
|
||||
},
|
||||
{
|
||||
"id": "single",
|
||||
"label": "Свободны"
|
||||
},
|
||||
{
|
||||
"id": "after-breakup",
|
||||
"label": "После расставания"
|
||||
},
|
||||
{
|
||||
"id": "complicated",
|
||||
"label": "Все сложно"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "analysis-target"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "analysis-target",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Кого анализируем?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "current-partner",
|
||||
"label": "Текущего партнера"
|
||||
},
|
||||
{
|
||||
"id": "crush",
|
||||
"label": "Человека, который нравится"
|
||||
},
|
||||
{
|
||||
"id": "ex-partner",
|
||||
"label": "Бывшего"
|
||||
},
|
||||
{
|
||||
"id": "future-partner",
|
||||
"label": "Будущую встречу"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"rules": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"current-partner"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "current-partner-age"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"crush"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "crush-age"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"ex-partner"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "ex-partner-age"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "analysis-target",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"future-partner"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "future-partner-age"
|
||||
}
|
||||
],
|
||||
"defaultNextScreenId": "current-partner-age"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "current-partner-age",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Возраст текущего партнера",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "under-29",
|
||||
"label": "До 29"
|
||||
},
|
||||
{
|
||||
"id": "30-39",
|
||||
"label": "30-39"
|
||||
},
|
||||
{
|
||||
"id": "40-49",
|
||||
"label": "40-49"
|
||||
},
|
||||
{
|
||||
"id": "50-59",
|
||||
"label": "50-59"
|
||||
},
|
||||
{
|
||||
"id": "60-plus",
|
||||
"label": "60+"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"rules": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "current-partner-age",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"under-29"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "age-refine"
|
||||
}
|
||||
],
|
||||
"defaultNextScreenId": "partner-ethnicity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "crush-age",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Возраст человека, который нравится",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "under-29",
|
||||
"label": "До 29"
|
||||
},
|
||||
{
|
||||
"id": "30-39",
|
||||
"label": "30-39"
|
||||
},
|
||||
{
|
||||
"id": "40-49",
|
||||
"label": "40-49"
|
||||
},
|
||||
{
|
||||
"id": "50-59",
|
||||
"label": "50-59"
|
||||
},
|
||||
{
|
||||
"id": "60-plus",
|
||||
"label": "60+"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"show": false
|
||||
},
|
||||
"navigation": {
|
||||
"rules": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "crush-age",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"under-29"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "age-refine"
|
||||
}
|
||||
],
|
||||
"defaultNextScreenId": "partner-ethnicity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ex-partner-age",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Возраст бывшего",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "under-29",
|
||||
"label": "До 29"
|
||||
},
|
||||
{
|
||||
"id": "30-39",
|
||||
"label": "30-39"
|
||||
},
|
||||
{
|
||||
"id": "40-49",
|
||||
"label": "40-49"
|
||||
},
|
||||
{
|
||||
"id": "50-59",
|
||||
"label": "50-59"
|
||||
},
|
||||
{
|
||||
"id": "60-plus",
|
||||
"label": "60+"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"rules": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "ex-partner-age",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"under-29"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "age-refine"
|
||||
}
|
||||
],
|
||||
"defaultNextScreenId": "partner-ethnicity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "future-partner-age",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Возраст будущего партнера",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "under-29",
|
||||
"label": "До 29"
|
||||
},
|
||||
{
|
||||
"id": "30-39",
|
||||
"label": "30-39"
|
||||
},
|
||||
{
|
||||
"id": "40-49",
|
||||
"label": "40-49"
|
||||
},
|
||||
{
|
||||
"id": "50-59",
|
||||
"label": "50-59"
|
||||
},
|
||||
{
|
||||
"id": "60-plus",
|
||||
"label": "60+"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"rules": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "future-partner-age",
|
||||
"operator": "includesAny",
|
||||
"optionIds": [
|
||||
"under-29"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nextScreenId": "age-refine"
|
||||
}
|
||||
],
|
||||
"defaultNextScreenId": "partner-ethnicity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "age-refine",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Уточните чуть точнее",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Чтобы портрет был максимально похож.",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "18-21",
|
||||
"label": "18-21"
|
||||
},
|
||||
{
|
||||
"id": "22-25",
|
||||
"label": "22-25"
|
||||
},
|
||||
{
|
||||
"id": "26-29",
|
||||
"label": "26-29"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "partner-ethnicity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partner-ethnicity",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Этническая принадлежность твоей второй половинки?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "white",
|
||||
"label": "White"
|
||||
},
|
||||
{
|
||||
"id": "hispanic",
|
||||
"label": "Hispanic / Latino"
|
||||
},
|
||||
{
|
||||
"id": "african",
|
||||
"label": "African / African-American"
|
||||
},
|
||||
{
|
||||
"id": "asian",
|
||||
"label": "Asian"
|
||||
},
|
||||
{
|
||||
"id": "south-asian",
|
||||
"label": "Indian / South Asian"
|
||||
},
|
||||
{
|
||||
"id": "middle-eastern",
|
||||
"label": "Middle Eastern / Arab"
|
||||
},
|
||||
{
|
||||
"id": "indigenous",
|
||||
"label": "Native American / Indigenous"
|
||||
},
|
||||
{
|
||||
"id": "no-preference",
|
||||
"label": "No preference"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "partner-eyes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partner-eyes",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Что из этого «про глаза»?",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "warm-glow",
|
||||
"label": "Тёплые искры на свету"
|
||||
},
|
||||
{
|
||||
"id": "clear-depth",
|
||||
"label": "Прозрачная глубина"
|
||||
},
|
||||
{
|
||||
"id": "green-sheen",
|
||||
"label": "Зелёный отлив на границе зрачка"
|
||||
},
|
||||
{
|
||||
"id": "steel-glint",
|
||||
"label": "Холодный стальной отблеск"
|
||||
},
|
||||
{
|
||||
"id": "deep-shadow",
|
||||
"label": "Насыщенная темнота"
|
||||
},
|
||||
{
|
||||
"id": "dont-know",
|
||||
"label": "Не знаю"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "partner-hair-length"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "partner-hair-length",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Выберите длину волос",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "От неё зависит форма и настроение портрета.",
|
||||
"color": "muted"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "single",
|
||||
"options": [
|
||||
{
|
||||
"id": "short",
|
||||
"label": "Короткие"
|
||||
},
|
||||
{
|
||||
"id": "medium",
|
||||
"label": "Средние"
|
||||
},
|
||||
{
|
||||
"id": "long",
|
||||
"label": "Длинные"
|
||||
}
|
||||
]
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "burnout-support"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "burnout-support",
|
||||
"template": "list",
|
||||
"title": {
|
||||
"text": "Когда ты выгораешь, тебе нужно чтобы партнёр...",
|
||||
"font": "manrope",
|
||||
"weight": "bold"
|
||||
},
|
||||
"list": {
|
||||
"selectionType": "multi",
|
||||
"options": [
|
||||
{
|
||||
"id": "reassure",
|
||||
"label": "Признал ваше разочарование и успокоил"
|
||||
},
|
||||
{
|
||||
"id": "emotional-support",
|
||||
"label": "Дал эмоциональную опору и безопасное пространство"
|
||||
},
|
||||
{
|
||||
"id": "take-over",
|
||||
"label": "Перехватил быт/дела, чтобы вы восстановились"
|
||||
},
|
||||
{
|
||||
"id": "energize",
|
||||
"label": "Вдохнул энергию через цель и короткий план действий"
|
||||
},
|
||||
{
|
||||
"id": "switch-positive",
|
||||
"label": "Переключил на позитив: прогулка, кино, смешные истории"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Continue",
|
||||
"show": false
|
||||
},
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "special-offer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "special-offer",
|
||||
"template": "coupon",
|
||||
"header": {
|
||||
"show": false
|
||||
},
|
||||
"title": {
|
||||
"text": "Тебе повезло!",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"align": "center"
|
||||
},
|
||||
"subtitle": {
|
||||
"text": "Ты получил специальную эксклюзивную скидку на 94%",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted",
|
||||
"align": "center"
|
||||
},
|
||||
"copiedMessage": "Промокод \"{code}\" скопирован!",
|
||||
"coupon": {
|
||||
"title": {
|
||||
"text": "Special Offer",
|
||||
"font": "manrope",
|
||||
"weight": "bold",
|
||||
"color": "primary"
|
||||
},
|
||||
"offer": {
|
||||
"title": {
|
||||
"text": "94% OFF",
|
||||
"font": "manrope",
|
||||
"weight": "black",
|
||||
"color": "card",
|
||||
"size": "4xl"
|
||||
},
|
||||
"description": {
|
||||
"text": "Одноразовая эксклюзивная скидка",
|
||||
"font": "inter",
|
||||
"weight": "semiBold",
|
||||
"color": "card"
|
||||
}
|
||||
},
|
||||
"promoCode": {
|
||||
"text": "HAIR50",
|
||||
"font": "inter",
|
||||
"weight": "semiBold"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Скопируйте или нажмите Continue",
|
||||
"font": "inter",
|
||||
"weight": "medium",
|
||||
"color": "muted",
|
||||
"size": "sm"
|
||||
}
|
||||
},
|
||||
"bottomActionButton": {
|
||||
"text": "Continue"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@ -1,20 +1,50 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { BAKED_FUNNELS } from "./bakedFunnels";
|
||||
import type { FunnelDefinition } from "./types";
|
||||
|
||||
import { FunnelDefinition } from "./types";
|
||||
function resolveBakedFunnel(funnelId: string): FunnelDefinition {
|
||||
const funnel = BAKED_FUNNELS[funnelId];
|
||||
|
||||
if (!funnel) {
|
||||
throw new Error(`Funnel '${funnelId}' is not baked.`);
|
||||
}
|
||||
|
||||
return funnel;
|
||||
}
|
||||
|
||||
function cloneFunnel<T>(value: T): T {
|
||||
if (typeof structuredClone === "function") {
|
||||
return structuredClone(value);
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
export async function loadFunnelDefinition(
|
||||
funnelId: string
|
||||
): Promise<FunnelDefinition> {
|
||||
const filePath = path.join(
|
||||
process.cwd(),
|
||||
"public",
|
||||
"funnels",
|
||||
`${funnelId}.json`
|
||||
);
|
||||
const funnel = resolveBakedFunnel(funnelId);
|
||||
|
||||
const raw = await fs.readFile(filePath, "utf-8");
|
||||
const parsed = JSON.parse(raw) as FunnelDefinition;
|
||||
|
||||
return parsed;
|
||||
return cloneFunnel(funnel);
|
||||
}
|
||||
|
||||
export function peekBakedFunnelDefinition(
|
||||
funnelId: string
|
||||
): FunnelDefinition {
|
||||
return resolveBakedFunnel(funnelId);
|
||||
}
|
||||
|
||||
export function listBakedFunnelIds(): string[] {
|
||||
return Object.keys(BAKED_FUNNELS);
|
||||
}
|
||||
|
||||
export function listBakedFunnelScreenParams(): Array<{
|
||||
funnelId: string;
|
||||
screenId: string;
|
||||
}> {
|
||||
return Object.entries(BAKED_FUNNELS).flatMap(([funnelId, funnel]) =>
|
||||
funnel.screens.map((screen) => ({
|
||||
funnelId,
|
||||
screenId: screen.id,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,12 +41,19 @@ function satisfiesCondition(
|
||||
}
|
||||
}
|
||||
|
||||
function satisfiesRule(rule: NavigationRuleDefinition, answers: FunnelAnswers): boolean {
|
||||
if (!rule.conditions || rule.conditions.length === 0) {
|
||||
export function matchesNavigationConditions(
|
||||
conditions: NavigationConditionDefinition[] | undefined,
|
||||
answers: FunnelAnswers
|
||||
): boolean {
|
||||
if (!conditions || conditions.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rule.conditions.every((condition) => satisfiesCondition(condition, answers));
|
||||
return conditions.every((condition) => satisfiesCondition(condition, answers));
|
||||
}
|
||||
|
||||
function satisfiesRule(rule: NavigationRuleDefinition, answers: FunnelAnswers): boolean {
|
||||
return matchesNavigationConditions(rule.conditions, answers);
|
||||
}
|
||||
|
||||
export function resolveNextScreenId(
|
||||
|
||||
@ -47,7 +47,7 @@ export interface ListOptionDefinition {
|
||||
}
|
||||
|
||||
export interface BottomActionButtonDefinition {
|
||||
text: string;
|
||||
text?: string;
|
||||
cornerRadius?: "3xl" | "full";
|
||||
/** Controls whether button should be displayed. Defaults to true. */
|
||||
show?: boolean;
|
||||
@ -85,6 +85,13 @@ export interface NavigationDefinition {
|
||||
defaultNextScreenId?: string;
|
||||
}
|
||||
|
||||
type ScreenVariantOverrides<T> = Partial<Omit<T, "id" | "template" | "variants">>;
|
||||
|
||||
export interface ScreenVariantDefinition<T extends { id: string; template: string }> {
|
||||
conditions: NavigationConditionDefinition[];
|
||||
overrides: ScreenVariantOverrides<T>;
|
||||
}
|
||||
|
||||
export interface InfoScreenDefinition {
|
||||
id: string;
|
||||
template: "info";
|
||||
@ -99,6 +106,7 @@ export interface InfoScreenDefinition {
|
||||
};
|
||||
bottomActionButton?: BottomActionButtonDefinition;
|
||||
navigation?: NavigationDefinition;
|
||||
variants?: ScreenVariantDefinition<InfoScreenDefinition>[];
|
||||
}
|
||||
|
||||
export interface DateInputDefinition {
|
||||
@ -126,6 +134,7 @@ export interface DateScreenDefinition {
|
||||
};
|
||||
bottomActionButton?: BottomActionButtonDefinition;
|
||||
navigation?: NavigationDefinition;
|
||||
variants?: ScreenVariantDefinition<DateScreenDefinition>[];
|
||||
}
|
||||
|
||||
export interface CouponDefinition {
|
||||
@ -148,6 +157,7 @@ export interface CouponScreenDefinition {
|
||||
copiedMessage?: string; // "Промокод скопирован!" text
|
||||
bottomActionButton?: BottomActionButtonDefinition;
|
||||
navigation?: NavigationDefinition;
|
||||
variants?: ScreenVariantDefinition<CouponScreenDefinition>[];
|
||||
}
|
||||
|
||||
export interface FormFieldDefinition {
|
||||
@ -179,6 +189,7 @@ export interface FormScreenDefinition {
|
||||
validationMessages?: FormValidationMessages;
|
||||
bottomActionButton?: BottomActionButtonDefinition;
|
||||
navigation?: NavigationDefinition;
|
||||
variants?: ScreenVariantDefinition<FormScreenDefinition>[];
|
||||
}
|
||||
|
||||
|
||||
@ -196,6 +207,7 @@ export interface ListScreenDefinition {
|
||||
};
|
||||
bottomActionButton?: BottomActionButtonDefinition;
|
||||
navigation?: NavigationDefinition;
|
||||
variants?: ScreenVariantDefinition<ListScreenDefinition>[];
|
||||
}
|
||||
|
||||
export type ScreenDefinition = InfoScreenDefinition | DateScreenDefinition | CouponScreenDefinition | FormScreenDefinition | ListScreenDefinition;
|
||||
|
||||
78
src/lib/funnel/variants.ts
Normal file
78
src/lib/funnel/variants.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { matchesNavigationConditions } from "./navigation";
|
||||
import type {
|
||||
FunnelAnswers,
|
||||
ScreenDefinition,
|
||||
ScreenVariantDefinition,
|
||||
} from "./types";
|
||||
|
||||
function cloneScreen<T>(screen: T): T {
|
||||
if (typeof globalThis.structuredClone === "function") {
|
||||
return globalThis.structuredClone(screen);
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(screen)) as T;
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function deepMerge<T>(target: T, source: Partial<T> | undefined): T {
|
||||
if (!source) {
|
||||
return target;
|
||||
}
|
||||
|
||||
const result: T = Array.isArray(target)
|
||||
? ([...target] as unknown as T)
|
||||
: ({ ...(target as Record<string, unknown>) } as T);
|
||||
|
||||
for (const key of Object.keys(source) as (keyof T)[]) {
|
||||
const sourceValue = source[key];
|
||||
|
||||
if (sourceValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetValue = (result as Record<string, unknown>)[key as unknown as string];
|
||||
|
||||
if (isPlainObject(sourceValue)) {
|
||||
const baseValue = isPlainObject(targetValue) ? targetValue : {};
|
||||
(result as Record<string, unknown>)[key as unknown as string] = deepMerge(
|
||||
baseValue,
|
||||
sourceValue as Record<string, unknown>
|
||||
) as unknown as T[keyof T];
|
||||
continue;
|
||||
}
|
||||
|
||||
(result as Record<string, unknown>)[key as unknown as string] = sourceValue as unknown as T[keyof T];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function applyScreenOverrides<T extends ScreenDefinition>(
|
||||
screen: T,
|
||||
overrides: ScreenVariantDefinition<T>["overrides"]
|
||||
): T {
|
||||
const cloned = cloneScreen(screen);
|
||||
return deepMerge(cloned, overrides as Partial<T>);
|
||||
}
|
||||
|
||||
export function resolveScreenVariant<T extends ScreenDefinition>(
|
||||
screen: T,
|
||||
answers: FunnelAnswers
|
||||
): T {
|
||||
const variants = (screen as T & { variants?: ScreenVariantDefinition<T>[] }).variants;
|
||||
|
||||
if (!variants || variants.length === 0) {
|
||||
return screen;
|
||||
}
|
||||
|
||||
for (const variant of variants) {
|
||||
if (matchesNavigationConditions(variant.conditions, answers)) {
|
||||
return applyScreenOverrides(screen, variant.overrides);
|
||||
}
|
||||
}
|
||||
|
||||
return screen;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user