diff --git a/design-drafts/02_Checklist_source.html b/design-drafts/02_Checklist_source.html new file mode 100644 index 0000000..c6d97fb --- /dev/null +++ b/design-drafts/02_Checklist_source.html @@ -0,0 +1,2314 @@ + + + + + +Подбор техники для кухни · фабрика мебели «ЗОВ» + + + + + +
+ + +
+
+
+
ПОДБОР ТЕХНИКИ ДЛЯ ВАШЕЙ КУХНИ
+
+ + + + + + + + + +
+
сделано с душой
+
подбор кухонной техники · персональное предложение
+
+ +
+
+ Запрос будет передан: + Любовь АЛПЕЕВА + · lyubov.alpeeva@zovofficial.com +
+ изменить +
+ + +
+ + +
+
+
👋
+

Добро пожаловать!

+

+ Этот чек-лист поможет собрать запрос на подбор кухонной техники. + Заполните данные менеджера и клиента — потом сможете выбрать категории + и параметры. На любом шаге вы сможете вернуться сюда. +

+
+ +
+ ШАГ 1 +

👤 Менеджер фабрики «ЗОВ» (отправитель запроса)

+
+ + +
+
+ +
+ ШАГ 2 +

🏠 Клиент (для кого подбираем технику)

+
+ + + + +
+
+ +
+

ЧТО СДЕЛАЕМ ДАЛЬШЕ?

+

Выберите подходящий сценарий — вы сможете изменить решение позже

+
+ + +
+ +
+
+
+ + + + + + + +
+ + +

Категория · Cooking · Hob

+

🔥 Варочная панель

+

Параметры подбора и предпочтительные марки

+ +
+

Марки техники

+

+ У каждой марки две кнопки: ★ — предпочтительная (хочу в первую очередь), + ✓ — готов рассмотреть (альтернатива, если первая не подойдёт). + Можно выбирать любые сочетания. Повторный клик снимает отметку. +

+ +
+ Быстрый выбор: + + + +
+ + +
+ ПРЕМИУМ-СЕГМЕНТот 100 000 ₽ +
+
Miele
+
Gaggenau
+
V-Zug
+
Smeg
+
Asko
+
+
+ +
+

Технические параметры

+
+
Параметр
Варианты
+ +
Тип нагрева
+
+ + + + +
+ +
Ширина
+
+ + + + + +
+ +
Кол-во зон
+
+ + + + +
+ +
Цвет / монтаж
+
+ + + + +
+ +
Доп. функции
+
+ + + + +
+ +
Особенности (своими словами)
+
+
+
+ +
+ СРЕДНИЙ СЕГМЕНТ30 000 — 100 000 ₽ +
+
Bosch
+
Siemens
+
NEFF
+
Electrolux
+
AEG
+
Whirlpool
+
+
+ +
+ БЮДЖЕТ / РФ-ГАРАНТИЯдо 30 000 ₽ +
+
Kuppersberg
+
Maunfeld
+
Korting
+
Hansa
+
Beko
+
Haier
+
Weissgauff
+
Gorenje
+
Hotpoint
+
+
+
+ + +
+ + +
+ + +

Категория · Cooling

+

❄️ Холодильник

+ +
+

Марки техники

+
+ Быстрый выбор: + + + +
+ + +
+ ПРЕМИУМот 250 000 ₽ +
+
Miele
+
Liebherr
+
Gaggenau
+
Smeg
+
Asko
+
+
+ +
+

Технические параметры

+
+
Параметр
Варианты
+ +
Тип
+
+ + + + + +
+ +
Ширина ниши
+
+ + + + + +
+ +
Объём
+
+ + + + +
+ +
Цвет
+
+ + + + + +
+ +
Особенности
+
+ + + + + + +
+ +
Особенности (своими словами)
+
+
+
+ +
+ СРЕДНИЙ80 000 — 250 000 ₽ +
+
Bosch
+
Siemens
+
NEFF
+
Electrolux
+
AEG
+
LG
+
Samsung
+
Haier
+
+
+ +
+ БЮДЖЕТдо 80 000 ₽ +
+
Beko
+
Hansa
+
Atlant
+
Indesit
+
Hisense
+
Midea
+
Gorenje
+
+
+
+ + +
+ + +
+ + +

Категория · Dishwashing

+

💧 Посудомоечная машина

+ +
+

Марки техники

+
+ Быстрый выбор: + + + +
+ +
+ ПРЕМИУМот 150 000 ₽ +
+
Miele
+
Asko
+
Gaggenau
+
NEFF
+
Smeg
+
+
+ +
+

Технические параметры

+
+
Параметр
Варианты
+
Ширина
+
+ + +
+
Тип монтажа
+
+ + + +
+
Кол-во комплектов
+
+ + + +
+
Доп. функции
+
+ + + + + +
+
Особенности
+
+
+
+
+ СРЕДНИЙ50 000 — 150 000 ₽ +
+
Bosch
+
Siemens
+
Electrolux
+
AEG
+
+
+
+ БЮДЖЕТдо 50 000 ₽ +
+
Kuppersberg
+
Maunfeld
+
Hansa
+
Beko
+
Hotpoint
+
Weissgauff
+
Midea
+
Gorenje
+
+
+
+ + +
+ + +
+ + +

Категория · Ventilation

+

🌬️ Вытяжка

+ +
+

Марки техники

+
+ Быстрый выбор: + + + +
+ +
+ ПРЕМИУМот 80 000 ₽ +
+
Miele
+
Gaggenau
+
Falmec
+
Faber (premium)
+
Smeg
+
+
+ +
+

Технические параметры

+
+
Параметр
Варианты
+
Тип
+
+ + + + + + +
+
Ширина
+
+ + + + +
+
Цвет
+
+ + + + +
+
Режим
+
+ + + +
+
Производительность мин.
+
+ + + + +
+
Объём кухни (м³)
+
+
Особенности
+
+
+
+
+ СРЕДНИЙ25 000 — 80 000 ₽ +
+
Bosch
+
Siemens
+
NEFF
+
Asko
+
Elica
+
Best
+
Cata
+
+
+
+ БЮДЖЕТдо 25 000 ₽ +
+
Maunfeld
+
Krona
+
Korting
+
Kuppersberg
+
Hansa
+
Elikor
+
LEX
+
+
+
+ + +
+ + +
+ + +

Категория · Cooking · Oven

+

🍞 Духовой шкаф (± СВЧ)

+ +
+

Марки техники

+
+ Быстрый выбор: + + + +
+ +
+ ПРЕМИУМот 250 000 ₽ +
+
Miele
+
Gaggenau
+
V-Zug
+
NEFF
+
Smeg
+
Asko
+
+
+ +
+

Технические параметры

+
+
Параметр
Варианты
+
Конфигурация
+
+ + + + +
+
Цвет
+
+ + + + + +
+
Тип очистки
+
+ + + + + +
+
Доп. функции
+
+ + + + + + +
+
Особенности
+
+
+
+
+ СРЕДНИЙ80 000 — 250 000 ₽ +
+
Bosch
+
Siemens
+
Electrolux
+
AEG
+
Whirlpool
+
+
+
+ БЮДЖЕТдо 80 000 ₽ +
+
Kuppersberg
+
Maunfeld
+
Hansa
+
Beko
+
Hotpoint
+
Gorenje
+
Indesit
+
+
+
+ + +
+ + +
+ +

Категория · Microwave

+

📡 Микроволновая печь (отдельно)

+
+

Марки техники

+
+ Быстрый выбор: + + + +
+ +
+ ПОПУЛЯРНЫЕ15 000 — 80 000 ₽ +
+
Bosch
+
Siemens
+
NEFF
+
Miele
+
Samsung
+
LG
+
Panasonic
+
Kuppersberg
+
Maunfeld
+
+
+
+

Технические параметры

+
+
Параметр
Варианты
+
Тип
+
+ + +
+
Объём
+
+ + + +
+
Доп. функции
+
+ + + +
+
Особенности
+
+
+
+
+ +
+ + +
+ +

Категория · Coffee

+

☕ Кофемашина

+
+

Марки техники

+
+ Быстрый выбор: + + + +
+ +
+ ПРЕМИУМот 150 000 ₽ +
+
Miele
+
Gaggenau
+
Jura
+
Smeg
+
+
+
+

Технические параметры

+
+
Параметр
Варианты
+
Тип
+
+ + +
+
Технология
+
+ + + +
+
Доп. функции
+
+ + + +
+
Особенности
+
+
+
+
+ СРЕДНИЙ50 000 — 150 000 ₽ +
+
Bosch
+
Siemens
+
NEFF
+
De'Longhi
+
Philips
+
Saeco
+
Krups
+
+
+
+ БЮДЖЕТдо 50 000 ₽ +
+
Polaris
+
Redmond
+
Kitfort
+
+
+
+ +
+ + +
+ +

Категория · Laundry

+

🧺 Стиральная машина

+
+

Марки техники

+
+ Быстрый выбор: + + + +
+ +
+ ПРЕМИУМот 200 000 ₽ +
+
Miele
+
Asko
+
Smeg
+
+
+
+

Технические параметры

+
+
Параметр
Варианты
+
Тип
+
+ + +
+
Загрузка
+
+ + + + +
+
Глубина
+
+ + +
+
Доп. функции
+
+ + + + +
+
Особенности
+
+
+
+
+ СРЕДНИЙ50 000 — 200 000 ₽ +
+
Bosch
+
Siemens
+
LG
+
Samsung
+
Electrolux
+
AEG
+
Haier
+
+
+
+ БЮДЖЕТдо 50 000 ₽ +
+
Beko
+
Hansa
+
Indesit
+
Atlant
+
Candy
+
Gorenje
+
+
+
+ +
+ + +
+ + +

Запрос готов

+

📋 Запрос менеджеру для подбора аналогов

+

+ Ниже — текст запроса со всеми вашими параметрами и предпочтениями по маркам. + Передайте его удобным способом — по этому запросу мы выполним поиск и предложим + конкретные модели. +

+ +
+

Контактные данные

+
+
ФИО клиента
+
+
Телефон / Email
+
+
Адрес объекта
+
+
Бюджет (общий)
+
+
Свободные пожелания
+
+
+
+ +

Текст запроса (готов к отправке)

+
— заполните параметры и нажмите «Обновить» —
+ + +
+

СКОПИРОВАТЬ ЗАПРОС ДЛЯ МЕНЕДЖЕРА

+

Выберите удобный способ передачи запроса

+
+ + + + +
+
+
+
+ + +
+ +
✓ Автосохранено
+ + + + + diff --git a/miniapp/assets/app.js b/miniapp/assets/app.js index 9ceb0dd..03a98fd 100644 --- a/miniapp/assets/app.js +++ b/miniapp/assets/app.js @@ -175,10 +175,10 @@ function renderManagerHome(me) { // Quick actions const quickActions = [ - { icon: "camera", title: "Новый замер", subtitle: "С фото" }, - { icon: "cube", title: "3D просмотр", subtitle: "Проекты" }, - { icon: "bolt", title: "Коммуникации", subtitle: "Чек-лист" }, - { icon: "package", title: "Каталог техники", subtitle: "Встройка" }, + { icon: "camera", title: "Новый замер", subtitle: "С фото", href: null }, + { icon: "cube", title: "3D просмотр", subtitle: "Проекты", href: null }, + { icon: "bolt", title: "Коммуникации", subtitle: "Чек-лист", href: null }, + { icon: "package", title: "Подбор техники", subtitle: "Встройка + AI", href: "#/podbor" }, ]; app.appendChild(el(`
Быстрые действия
`)); const grid = el(`
`); @@ -190,7 +190,11 @@ function renderManagerHome(me) {
${qa.subtitle}
`); - card.addEventListener("click", () => { haptic("impact"); tg?.showAlert?.(`«${qa.title}» — скоро`); }); + card.addEventListener("click", () => { + haptic("impact"); + if (qa.href) location.hash = qa.href; + else tg?.showAlert?.(`«${qa.title}» — скоро`); + }); grid.appendChild(card); }); app.appendChild(grid); @@ -350,8 +354,16 @@ function renderError() { /* ----------------- Init ----------------- */ async function init() { setupTelegram(); + // Hash-роутер: позволяет открывать подэкраны (например подбор) напрямую + window.addEventListener("hashchange", routeByHash); + try { const me = await fetchMe(); + window.__zovMe = me; // кешируем профиль для подэкранов + if (location.hash.startsWith("#/podbor")) { + Podbor.mount(app); + return; + } if (me.role === "manager") renderManager(me); else renderClient(me); } catch (e) { @@ -360,4 +372,16 @@ async function init() { } } +function routeByHash() { + if (location.hash.startsWith("#/podbor")) { + Podbor.mount(app); + } else { + // Главный экран по роли + const me = window.__zovMe; + if (!me) { init(); return; } + if (me.role === "manager") renderManager(me); + else renderClient(me); + } +} + init(); diff --git a/miniapp/assets/icons.js b/miniapp/assets/icons.js index f27ee54..9549df8 100644 --- a/miniapp/assets/icons.js +++ b/miniapp/assets/icons.js @@ -45,4 +45,18 @@ const ICONS = { chat: ``, home: ``, + + arrow_left: ``, + + /* Категории техники — line, stroke 1.5 */ + cat_hob: ``, + cat_fridge: ``, + cat_dw: ``, + cat_hood: ``, + cat_oven: ``, + cat_microwave: ``, + cat_coffee: ``, + cat_washer: ``, + + check: ``, }; diff --git a/miniapp/assets/podbor.config.js b/miniapp/assets/podbor.config.js new file mode 100644 index 0000000..63bcc54 --- /dev/null +++ b/miniapp/assets/podbor.config.js @@ -0,0 +1,101 @@ +/* ============================================================ + Подбор техники — статические данные (адаптация 02_Чек-лист_клиенту.html) + ============================================================ */ + +const PODBOR_CATEGORIES = [ + { key: "fridge", icon: "cat_fridge", label: "Холодильник" }, + { key: "hob", icon: "cat_hob", label: "Варочная панель" }, + { key: "oven", icon: "cat_oven", label: "Духовой шкаф" }, + { key: "dw", icon: "cat_dw", label: "Посудомоечная" }, + { key: "hood", icon: "cat_hood", label: "Вытяжка" }, + { key: "microwave", icon: "cat_microwave", label: "Микроволновка" }, + { key: "coffee", icon: "cat_coffee", label: "Кофемашина" }, + { key: "washer", icon: "cat_washer", label: "Стиральная машина" }, +]; + +const PODBOR_BUDGET_TIERS = [ + { key: "premium", label: "Премиум", hint: "лучшее без оглядки на цену" }, + { key: "middle", label: "Средний", hint: "разумный баланс цена/функции" }, + { key: "budget", label: "Бюджет", hint: "только нужное" }, +]; + +const PODBOR_FAMILY = [ + { key: "single", label: "1 взрослый" }, + { key: "couple", label: "Пара" }, + { key: "family", label: "Семья с детьми" }, + { key: "multigen", label: "2+ поколения" }, +]; + +const PODBOR_COOKING = [ + { key: "daily", label: "Ежедневно" }, + { key: "weekly", label: "3–5 раз в неделю" }, + { key: "rare", label: "По выходным или реже" }, +]; + +const PODBOR_INFRA = { + stove: [ + { key: "induction", label: "Индукция / 380 В" }, + { key: "el_220", label: "Электрика 220 В" }, + { key: "gas", label: "Газ" }, + { key: "any", label: "Не знаю / любой" }, + ], + vent: [ + { key: "shaft", label: "Шахта вентиляции есть" }, + { key: "no_shaft", label: "Только рециркуляция" }, + { key: "unknown", label: "Не знаю" }, + ], +}; + +const PODBOR_TECHNIQUES = [ + { key: "bake", label: "Выпечка" }, + { key: "steam", label: "На пару" }, + { key: "grill", label: "Гриль" }, + { key: "wok", label: "Wok / стир-фрай" }, + { key: "low_t", label: "Низкотемпературное" }, + { key: "smart", label: "Умные режимы / Smart" }, +]; + +/* Бренды для каждой категории — для чипов с тирами. + Сокращённый набор; полный список можно расширить из исходного HTML. */ +const PODBOR_BRANDS = { + fridge: { + premium: ["Liebherr", "Miele", "Sub-Zero", "V-ZUG"], + middle: ["Bosch", "Siemens", "Samsung", "LG"], + budget: ["Indesit", "Beko", "Hotpoint"], + }, + hob: { + premium: ["Miele", "Gaggenau", "AEG"], + middle: ["Bosch", "Siemens", "Electrolux", "Hansa"], + budget: ["Hotpoint", "Beko", "Indesit"], + }, + oven: { + premium: ["Miele", "Gaggenau", "Neff"], + middle: ["Bosch", "Siemens", "Electrolux", "AEG"], + budget: ["Hansa", "Beko", "Hotpoint"], + }, + dw: { + premium: ["Miele", "Asko", "V-ZUG"], + middle: ["Bosch", "Siemens", "Electrolux"], + budget: ["Hansa", "Beko", "Indesit"], + }, + hood: { + premium: ["Miele", "Falmec", "Faber"], + middle: ["Bosch", "Siemens", "Elica"], + budget: ["Hansa", "Hotpoint", "Maunfeld"], + }, + microwave: { + premium: ["Miele", "Neff"], + middle: ["Bosch", "Siemens", "Samsung", "LG"], + budget: ["Whirlpool", "Hansa", "Beko"], + }, + coffee: { + premium: ["Miele", "Jura", "De'Longhi PrimaDonna"], + middle: ["De'Longhi", "Saeco", "Bosch"], + budget: ["Krups", "Philips"], + }, + washer: { + premium: ["Miele", "Asko", "V-ZUG"], + middle: ["Bosch", "Siemens", "Samsung", "LG"], + budget: ["Indesit", "Hotpoint", "Beko"], + }, +}; diff --git a/miniapp/assets/podbor.css b/miniapp/assets/podbor.css new file mode 100644 index 0000000..1fd84ea --- /dev/null +++ b/miniapp/assets/podbor.css @@ -0,0 +1,434 @@ +/* ============================================================ + Подбор техники — стили (Editorial Calm) + ============================================================ */ + +/* ----- Header ----- */ +.podbor-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--s4); +} + +.podbor-back { + width: 28px; + height: 28px; + display: grid; + place-items: center; + color: var(--ink); + cursor: pointer; +} + +.podbor-back svg { width: 20px; height: 20px; } + +.podbor-title { + font-family: var(--font-mono); + font-size: 11px; + font-weight: 500; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--muted); +} + +/* ----- Progress ----- */ +.podbor-progress { + margin-bottom: var(--s5); +} + +.podbor-progress-bar { + height: 2px; + background: var(--line); + border-radius: var(--r-pill); + overflow: hidden; + margin-bottom: 6px; +} + +.podbor-progress-bar .bar { + height: 100%; + background: var(--accent-2); + border-radius: inherit; + transition: width 0.3s ease; +} + +.podbor-progress-meta { + display: flex; + justify-content: space-between; + font-family: var(--font-mono); + font-size: 10px; + font-weight: 500; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--muted); +} + +.podbor-progress-meta .num { color: var(--ink); } + +/* ----- Step container ----- */ +.podbor-screen { animation: fadeIn 0.2s ease; } +@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } } + +.podbor-step { + display: flex; + flex-direction: column; + gap: var(--s4); +} + +.display-title { + font-family: var(--font-display); + font-style: italic; + font-size: 32px; + font-weight: 400; + line-height: 1.05; + letter-spacing: -0.02em; + color: var(--ink); + margin: 0; +} + +.display-title .accent { color: var(--accent-2); } + +.lede { + font-size: 14.5px; + line-height: 1.45; + color: var(--ink-2); + margin: 0; +} + +/* ----- Form basics ----- */ +.field { + display: flex; + flex-direction: column; + gap: 4px; +} + +.field-label { + font-family: var(--font-mono); + font-size: 10px; + font-weight: 500; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--muted); +} + +.field input, +.field textarea, +.field-inline input { + font-family: var(--font-ui); + font-size: 15px; + color: var(--ink); + background: var(--card); + border: 1px solid var(--line-strong); + border-radius: var(--r-btn); + padding: 11px 12px; + outline: none; + transition: border-color 0.12s; + width: 100%; +} + +.field input:focus, +.field textarea:focus, +.field-inline input:focus { border-color: var(--accent-2); } + +.field textarea { resize: vertical; min-height: 64px; font-family: var(--font-ui); } + +.form-row { display: flex; flex-direction: column; gap: var(--s2); } +.form-row.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); } + +/* ----- Block grouping ----- */ +.block { + background: var(--card); + border: 1px solid var(--line-strong); + border-radius: var(--r-card); + padding: var(--s4); + display: flex; + flex-direction: column; + gap: var(--s3); +} + +.block-head { + font-family: var(--font-mono); + font-size: 11px; + font-weight: 500; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--muted); +} + +.hint { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.10em; + text-transform: uppercase; + color: var(--muted); +} + +/* ----- Categories grid ----- */ +.cat-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--s2); +} + +.cat-card { + background: var(--card); + border: 1px solid var(--line-strong); + border-radius: var(--r-card); + padding: 12px; + display: flex; + flex-direction: column; + gap: 8px; + align-items: flex-start; + position: relative; + cursor: pointer; + transition: background 0.12s, border-color 0.12s; + min-height: 84px; +} + +.cat-card:active { background: var(--paper-2); } + +.cat-card.active { + border-color: var(--ink); + background: var(--paper-2); +} + +.cat-card .cat-icon { + width: 26px; height: 26px; + display: grid; place-items: center; + color: var(--ink); +} + +.cat-card .cat-icon svg { width: 22px; height: 22px; stroke-width: 1.4; } + +.cat-card .cat-label { + font-family: var(--font-ui); + font-size: 13.5px; + font-weight: 600; + letter-spacing: -0.01em; + color: var(--ink); +} + +.cat-card .cat-check { + position: absolute; + top: 10px; right: 10px; + width: 18px; height: 18px; + background: var(--accent-2); + color: var(--paper); + border-radius: var(--r-pill); + display: grid; place-items: center; +} + +.cat-card .cat-check svg { width: 12px; height: 12px; stroke-width: 2.5; } + +/* ----- Niches ----- */ +.niche-list { display: flex; flex-direction: column; gap: var(--s2); } +.niche-row { display: flex; flex-direction: column; gap: 4px; } +.niche-label { + font-family: var(--font-ui); + font-size: 13px; + font-weight: 500; + color: var(--ink-2); +} +.niche-inputs { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; } +.niche-inputs input { + font-family: var(--font-mono); + font-size: 13px; + padding: 9px 10px; + text-align: center; + background: var(--paper); + border: 1px solid var(--line); + border-radius: var(--r-tag); + color: var(--ink); +} + +.budget-list { display: flex; flex-direction: column; gap: var(--s2); } +.field-inline { + display: flex; align-items: center; justify-content: space-between; gap: var(--s3); + font-family: var(--font-ui); font-size: 14px; color: var(--ink-2); +} +.field-inline input { + width: 130px; padding: 8px 10px; font-family: var(--font-mono); font-size: 13px; + background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-tag); + text-align: right; color: var(--ink); +} + +/* ----- Option chips ----- */ +.opt-list { display: flex; flex-wrap: wrap; gap: 6px; } + +.opt { + font-family: var(--font-ui); + font-size: 13px; + font-weight: 500; + padding: 8px 12px; + border-radius: var(--r-pill); + border: 1px solid var(--line-strong); + background: var(--paper); + color: var(--ink-2); + cursor: pointer; + transition: all 0.12s; +} + +.opt:active { transform: scale(0.97); } + +.opt.on { + background: var(--ink); + color: var(--paper); + border-color: var(--ink); +} + +/* ----- Brand chips ----- */ +.tier-row { display: flex; flex-direction: column; gap: 6px; padding: 8px 0; border-top: 1px solid var(--line); } +.tier-row:first-child { border-top: none; padding-top: 0; } + +.tier-label { + font-family: var(--font-mono); + font-size: 9.5px; + font-weight: 500; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--muted); +} + +.brand-chips { display: flex; flex-wrap: wrap; gap: 6px; } + +.chip { + font-family: var(--font-ui); + font-size: 12.5px; + font-weight: 500; + padding: 6px 10px; + border-radius: var(--r-tag); + border: 1px solid var(--line-strong); + background: var(--paper); + color: var(--muted); + cursor: pointer; + transition: all 0.12s; + position: relative; +} + +.chip.status-preferred { + background: var(--accent-2); + color: var(--paper); + border-color: var(--accent-2); + font-weight: 600; +} + +.chip.status-preferred::before { content: "★ "; } + +.chip.status-acceptable { + background: var(--paper-2); + color: var(--ink); + border-color: var(--accent-2); +} + +.chip.status-acceptable::before { content: "✓ "; } + +/* ----- Summary ----- */ +.summary-block { gap: 8px; } +.summary-block .kv { + display: flex; justify-content: space-between; align-items: baseline; + padding: 6px 0; + border-bottom: 1px solid var(--line); + font-family: var(--font-ui); + font-size: 14px; +} +.summary-block .kv:last-child { border-bottom: none; } +.summary-block .kv span { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--muted); +} +.summary-block .kv strong { font-weight: 600; color: var(--ink); } + +/* ----- CTA / buttons ----- */ +.podbor-cta-row { + display: flex; gap: var(--s2); + margin-top: var(--s3); +} + +.btn-primary, .btn-secondary { + flex: 1; + font-family: var(--font-ui); + font-size: 14.5px; + font-weight: 600; + letter-spacing: -0.01em; + padding: 12px var(--s4); + border-radius: var(--r-btn); + cursor: pointer; + transition: opacity 0.12s, background 0.12s; +} + +.btn-primary { + background: var(--ink); + color: var(--paper); +} + +.btn-primary:active { opacity: 0.85; } +.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } + +.btn-secondary { + background: transparent; + color: var(--ink); + border: 1px solid var(--line-strong); +} + +.btn-secondary:active { background: var(--paper-2); } + +/* ----- Submit result ----- */ +.submit-result { margin-top: var(--s3); } + +.submit-result .success { + display: flex; align-items: center; gap: var(--s3); + background: var(--paper-2); + border: 1px solid var(--accent-2); + border-radius: var(--r-card); + padding: var(--s4); +} + +.submit-result .success-icon { + width: 36px; height: 36px; + background: var(--accent-2); + color: var(--paper); + border-radius: var(--r-pill); + display: grid; place-items: center; + flex-shrink: 0; +} + +.submit-result .success-icon svg { width: 20px; height: 20px; stroke-width: 2.5; } + +.submit-result .success-title { + font-family: var(--font-ui); + font-size: 15px; + font-weight: 600; + color: var(--ink); +} + +.submit-result .success-sub { + font-family: var(--font-mono); + font-size: 10.5px; + letter-spacing: 0.10em; + text-transform: uppercase; + color: var(--muted); + margin-top: 2px; +} + +.submit-result .error { + background: rgba(192, 57, 43, 0.08); + border: 1px solid rgba(192, 57, 43, 0.30); + color: #8C3F1E; + border-radius: var(--r-card); + padding: var(--s3) var(--s4); + font-size: 14px; +} + +.spinner-inline { + display: inline-block; + width: 14px; height: 14px; + border: 1.5px solid rgba(251,247,240,0.3); + border-top-color: var(--paper); + border-radius: var(--r-pill); + animation: spin 0.7s linear infinite; + margin-right: 6px; + vertical-align: -2px; +} + +.empty { text-align: center; color: var(--muted); padding: var(--s7) 0; } diff --git a/miniapp/assets/podbor.js b/miniapp/assets/podbor.js new file mode 100644 index 0000000..7c16dee --- /dev/null +++ b/miniapp/assets/podbor.js @@ -0,0 +1,519 @@ +/* ============================================================ + Подбор техники — render, state, navigation, submit + ============================================================ */ + +const Podbor = (function () { + const STORAGE_KEY = "zov-podbor-v1"; + const STEPS = ["intro", "categories", "context", "infra", "scenario", "brands", "summary"]; + + let state = loadState(); + let root = null; + let currentStep = "intro"; + + function loadState() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) return JSON.parse(raw); + } catch (e) {} + return defaultState(); + } + + function defaultState() { + return { + client_name: "", + client_phone: "", + address: "", + budget_total: "", + categories: [], // ['fridge','hob',...] + niches: {}, // { fridge:{w,h,d}, hob:{w,d}, oven:{w,h,d}, dw:{w,h,d} } + budget_by_cat: {},// { fridge:80000, hob:50000, ... } + infra: { stove: "", vent: "" }, + scenario: { family: "", cooking: "", techniques: [], guests: "" }, + brands: {}, // { fridge: {Bosch:'preferred', Liebherr:'preferred'}, ... } + notes: "", + }; + } + + function saveState() { + try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (e) {} + } + + function update(patch) { + state = { ...state, ...patch }; + saveState(); + } + + /* ===================== Render entry ===================== */ + + function mount(container) { + root = container; + document.body.classList.remove("has-bottom-nav"); + const oldNav = document.getElementById("bottom-nav"); + if (oldNav) oldNav.remove(); + render(); + } + + function go(step) { + if (!STEPS.includes(step)) return; + currentStep = step; + render(); + window.scrollTo({ top: 0, behavior: "smooth" }); + haptic && haptic("impact"); + } + + function render() { + if (!root) return; + root.innerHTML = ""; + root.appendChild(renderHeader()); + root.appendChild(renderProgress()); + const screen = el(`
`); + root.appendChild(screen); + + switch (currentStep) { + case "intro": screen.appendChild(renderIntro()); break; + case "categories": screen.appendChild(renderCategories()); break; + case "context": screen.appendChild(renderContext()); break; + case "infra": screen.appendChild(renderInfra()); break; + case "scenario": screen.appendChild(renderScenario()); break; + case "brands": screen.appendChild(renderBrands()); break; + case "summary": screen.appendChild(renderSummary()); break; + } + } + + /* ===================== Header & progress ===================== */ + + function renderHeader() { + const h = el(` +
+ +
Подбор техники
+
+
+ `); + h.querySelector(".podbor-back").addEventListener("click", () => { + const idx = STEPS.indexOf(currentStep); + if (idx <= 0) { + // Выход из подбора в главный экран кабинета + location.hash = ""; + location.reload(); + } else { + go(STEPS[idx - 1]); + } + }); + return h; + } + + function renderProgress() { + const idx = STEPS.indexOf(currentStep); + const total = STEPS.length; + const pct = Math.round(((idx + 1) / total) * 100); + const labels = ["Старт", "Категории", "Контекст", "Инфра", "Сценарий", "Бренды", "Подбор"]; + return el(` +
+
+
+ ${labels[idx]}${idx + 1}/${total} +
+
+ `); + } + + /* ===================== Step: intro ===================== */ + + function renderIntro() { + const node = el(` +
+

Подбор техники
для клиента

+

7 коротких шагов. Указываем категории, бюджет, инфраструктуру и предпочтения. Дальше AI сам соберёт предложение.

+ +
+ +
+
+ + +
+ +
+ +
+
+ `); + bindInputs(node); + bindNav(node); + return node; + } + + /* ===================== Step: categories ===================== */ + + function renderCategories() { + const grid = PODBOR_CATEGORIES.map(c => ` + + `).join(""); + + const node = el(` +
+

Какую технику
подбираем?

+

Выберите все категории, что нужно подобрать клиенту.

+
${grid}
+
+ + +
+
+ `); + node.querySelectorAll(".cat-card").forEach(card => { + card.addEventListener("click", () => { + const cat = card.dataset.cat; + const next = state.categories.includes(cat) + ? state.categories.filter(x => x !== cat) + : [...state.categories, cat]; + update({ categories: next }); + render(); + }); + }); + bindNav(node); + return node; + } + + /* ===================== Step: context (ниши + бюджет по категориям) ===================== */ + + function renderContext() { + const builtinCats = ["fridge", "hob", "oven", "dw"]; // встройка + const niches = builtinCats.filter(c => state.categories.includes(c)).map(c => { + const cat = PODBOR_CATEGORIES.find(x => x.key === c); + const n = state.niches[c] || {}; + return ` +
+
${cat.label}
+
+ + + +
+
+ `; + }).join(""); + + const budgets = state.categories.map(c => { + const cat = PODBOR_CATEGORIES.find(x => x.key === c); + const v = state.budget_by_cat[c] || ""; + return ` + + `; + }).join(""); + + const node = el(` +
+

Размеры
и бюджет

+

Если планируется встройка — укажите размеры ниш. Бюджет по категориям помогает AI распределить деньги.

+ + ${niches ? ` +
+
Ниши под встройку, мм
+
${niches}
+
+ ` : ""} + + ${budgets ? ` +
+
Бюджет по категориям, ₽
+
${budgets}
+
+ ` : ""} + +
+ + +
+
+ `); + node.querySelectorAll("[data-niche]").forEach(inp => { + inp.addEventListener("input", e => { + const [cat, dim] = e.target.dataset.niche.split("."); + const next = { ...state.niches, [cat]: { ...(state.niches[cat] || {}), [dim]: e.target.value } }; + update({ niches: next }); + }); + }); + node.querySelectorAll("[data-budget]").forEach(inp => { + inp.addEventListener("input", e => { + const cat = e.target.dataset.budget; + const next = { ...state.budget_by_cat, [cat]: e.target.value }; + update({ budget_by_cat: next }); + }); + }); + bindNav(node); + return node; + } + + /* ===================== Step: infra ===================== */ + + function renderInfra() { + const node = el(` +
+

Инфраструктура
кухни

+

Газ или электрика — главный вопрос для варочной. Шахта вентиляции — для вытяжки.

+
+
Подключение варочной
+
+ ${PODBOR_INFRA.stove.map(o => ` + + `).join("")} +
+
+
+
Вентиляция для вытяжки
+
+ ${PODBOR_INFRA.vent.map(o => ` + + `).join("")} +
+
+
+ + +
+
+ `); + node.querySelectorAll("[data-infra]").forEach(b => { + b.addEventListener("click", () => { + update({ infra: { ...state.infra, [b.dataset.infra]: b.dataset.val } }); + render(); + }); + }); + bindNav(node); + return node; + } + + /* ===================== Step: scenario ===================== */ + + function renderScenario() { + const node = el(` +
+

Сценарий
использования

+

Семья с детьми готовит иначе, чем пара. AI учтёт это в подборе.

+ +
+
Состав семьи
+
+ ${PODBOR_FAMILY.map(o => ` + + `).join("")} +
+
+ +
+
Частота готовки
+
+ ${PODBOR_COOKING.map(o => ` + + `).join("")} +
+
+ +
+
Любимые техники приготовления
+
+ ${PODBOR_TECHNIQUES.map(o => ` + + `).join("")} +
+
Можно несколько
+
+ +
+ + +
+
+ `); + node.querySelectorAll("[data-scenario]").forEach(b => { + b.addEventListener("click", () => { + update({ scenario: { ...state.scenario, [b.dataset.scenario]: b.dataset.val } }); + render(); + }); + }); + node.querySelectorAll("[data-tech]").forEach(b => { + b.addEventListener("click", () => { + const cur = state.scenario.techniques || []; + const key = b.dataset.tech; + const next = cur.includes(key) ? cur.filter(x => x !== key) : [...cur, key]; + update({ scenario: { ...state.scenario, techniques: next } }); + render(); + }); + }); + bindNav(node); + return node; + } + + /* ===================== Step: brands ===================== */ + + function renderBrands() { + if (!state.categories.length) { + return el(`
Сначала выберите категории.
`); + } + const blocks = state.categories.map(catKey => { + const cat = PODBOR_CATEGORIES.find(x => x.key === catKey); + const brands = PODBOR_BRANDS[catKey] || { premium: [], middle: [], budget: [] }; + const catState = state.brands[catKey] || {}; + const tierBlock = (tier) => ` +
+
${PODBOR_BUDGET_TIERS.find(t => t.key === tier).label}
+
+ ${(brands[tier] || []).map(b => { + const status = catState[b] || "none"; + return ``; + }).join("")} +
+
+ `; + return ` +
+
${cat.label}
+
★ Тап — предпочтительно · ✓ Двойной — допустимо · — Третий — снять
+ ${tierBlock("premium")}${tierBlock("middle")}${tierBlock("budget")} +
+ `; + }).join(""); + + const node = el(` +
+

Бренды
по категориям

+

Какие марки уважаете, какие — допустимы. AI сначала пробует preferred.

+ ${blocks} +
+ + +
+
+ `); + node.querySelectorAll(".chip[data-brand]").forEach(c => { + c.addEventListener("click", () => { + const catKey = c.dataset.cat, brand = c.dataset.brand; + const cur = (state.brands[catKey] || {})[brand] || "none"; + const nextStatus = cur === "none" ? "preferred" : cur === "preferred" ? "acceptable" : "none"; + const catBrands = { ...(state.brands[catKey] || {}) }; + if (nextStatus === "none") delete catBrands[brand]; + else catBrands[brand] = nextStatus; + update({ brands: { ...state.brands, [catKey]: catBrands } }); + render(); + }); + }); + bindNav(node); + return node; + } + + /* ===================== Step: summary + submit ===================== */ + + function renderSummary() { + const node = el(` +
+

Готово
к подбору

+

Проверьте и отправьте — AI вернёт предложение в чат с ботом.

+
+
Клиент${state.client_name || "—"}
+
Бюджет${state.budget_total ? state.budget_total + " ₽" : "—"}
+
Категорий${state.categories.length}
+
Семья${PODBOR_FAMILY.find(f => f.key === state.scenario.family)?.label || "—"}
+
Готовка${PODBOR_COOKING.find(f => f.key === state.scenario.cooking)?.label || "—"}
+
Подключение${PODBOR_INFRA.stove.find(f => f.key === state.infra.stove)?.label || "—"}
+
+ + + +
+ + +
+ +
+
+ `); + bindInputs(node); + bindNav(node); + node.querySelector("#submitBtn").addEventListener("click", () => onSubmit(node)); + return node; + } + + /* ===================== Submit ===================== */ + + async function onSubmit(node) { + const btn = node.querySelector("#submitBtn"); + const result = node.querySelector("#submitResult"); + btn.disabled = true; + btn.innerHTML = ' AI думает...'; + result.innerHTML = ""; + + if (!BACKEND_URL) { + result.innerHTML = `
BACKEND_URL не настроен (dev-режим).
`; + btn.disabled = false; btn.textContent = "Отправить · AI подберёт"; return; + } + + try { + const res = await fetch(`${BACKEND_URL}?path=podbor`, { + method: "POST", + body: JSON.stringify({ + initData: tg?.initData || "", + checklist: state, + client_name: state.client_name, + }), + }); + const data = await res.json(); + if (data.error) { + result.innerHTML = `
Ошибка: ${data.error}
`; + } else { + result.innerHTML = ` +
+
${ICONS.check}
+
+
Подбор отправлен в чат бота
+
Лид #${(data.id || "").slice(0, 6)} · откройте Telegram
+
+
+ `; + haptic && haptic("success"); + } + } catch (e) { + result.innerHTML = `
Сеть: ${e.message}
`; + } + btn.disabled = false; + btn.textContent = "Отправить ещё раз"; + } + + /* ===================== Helpers ===================== */ + + function bindInputs(node) { + node.querySelectorAll("[data-bind]").forEach(inp => { + inp.addEventListener("input", e => { + update({ [e.target.dataset.bind]: e.target.value }); + }); + }); + } + + function bindNav(node) { + node.querySelectorAll("[data-go]").forEach(b => { + b.addEventListener("click", () => go(b.dataset.go)); + }); + } + + return { mount, go, getState: () => state, reset: () => { state = defaultState(); saveState(); render(); } }; +})(); diff --git a/miniapp/index.html b/miniapp/index.html index ad8032c..cefda86 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -12,7 +12,8 @@ - + +
@@ -20,7 +21,9 @@
- - + + + +