mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 14:04:48 +00:00
miniapp: hierarchical wizard for fridge category (style D pictograms)
- New PODBOR_PARAMS schema with steps[] supporting single/multi + optionsBy branches - 11 fridge SVG pictograms in podbor.picts.js (style D — 3D perspective with shadow) - renderCategoryWizard with step-by-step flow, chips for prior answers, review screen - Legacy renderCategoryDetail still used for other 7 categories until migrated - Auto-advance on single-select, Дальше button for multi-select - Backend-compatible: per_cat[catKey].answers replaces .params/.features
This commit is contained in:
parent
cd5d92ea17
commit
17b112f061
71
.tmp_ssh.py
Normal file
71
.tmp_ssh.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
"""Tiny SSH helper for VPS setup. Used only in this session.
|
||||||
|
Usage:
|
||||||
|
python .tmp_ssh.py exec "<command>"
|
||||||
|
python .tmp_ssh.py upload <local> <remote>
|
||||||
|
"""
|
||||||
|
import sys, os, io, paramiko
|
||||||
|
# Force UTF-8 stdout so Cyrillic / arrows survive on Windows cp1251 console
|
||||||
|
try:
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
HOST = "94.241.170.144"
|
||||||
|
USER = "root"
|
||||||
|
PASS = "qywz*aCXwL2Sr7"
|
||||||
|
KEY_PATH = os.path.expanduser("~/.ssh/zov_vps_ed25519")
|
||||||
|
|
||||||
|
|
||||||
|
def make_client():
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
# Try key first, fall back to password
|
||||||
|
try:
|
||||||
|
client.connect(HOST, username=USER, key_filename=KEY_PATH, timeout=15)
|
||||||
|
except (paramiko.AuthenticationException, paramiko.SSHException, OSError):
|
||||||
|
client.connect(HOST, username=USER, password=PASS, timeout=15, allow_agent=False, look_for_keys=False)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, timeout=120):
|
||||||
|
c = make_client()
|
||||||
|
try:
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd, timeout=timeout)
|
||||||
|
rc = stdout.channel.recv_exit_status()
|
||||||
|
out = stdout.read().decode("utf-8", errors="replace")
|
||||||
|
err = stderr.read().decode("utf-8", errors="replace")
|
||||||
|
return rc, out, err
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
|
||||||
|
def upload(local, remote):
|
||||||
|
c = make_client()
|
||||||
|
try:
|
||||||
|
sftp = c.open_sftp()
|
||||||
|
sftp.put(local, remote)
|
||||||
|
sftp.close()
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = sys.argv[1:]
|
||||||
|
if not args:
|
||||||
|
print("commands: exec <cmd> | upload <local> <remote>")
|
||||||
|
sys.exit(1)
|
||||||
|
op = args[0]
|
||||||
|
if op == "exec":
|
||||||
|
rc, out, err = run(" ".join(args[1:]))
|
||||||
|
if out:
|
||||||
|
sys.stdout.write(out)
|
||||||
|
if err:
|
||||||
|
sys.stderr.write(err)
|
||||||
|
sys.exit(rc)
|
||||||
|
elif op == "upload":
|
||||||
|
upload(args[1], args[2])
|
||||||
|
print("uploaded")
|
||||||
|
else:
|
||||||
|
print("unknown op:", op)
|
||||||
|
sys.exit(1)
|
||||||
608
design-drafts/A _ Editorial Calm _ _ _ _ _.html
Normal file
608
design-drafts/A _ Editorial Calm _ _ _ _ _.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
27
design-drafts/README.md
Normal file
27
design-drafts/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Design drafts
|
||||||
|
|
||||||
|
Сюда складываются HTML/изображения с дизайн-концепциями MiniApp, сгенерированные через Claude Skills (Frontend design, Interactive prototype, и т.п.).
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. В Claude.ai прикрепить скил **Frontend design** или **Interactive prototype**.
|
||||||
|
2. Сгенерировать концепцию.
|
||||||
|
3. **Save as standalone HTML** → положить файл сюда (имя свободное, например `option-1-ios.html`).
|
||||||
|
4. Сказать «вот вариант» — я перенесу стили / лейаут / типографику в реальный `miniapp/`.
|
||||||
|
|
||||||
|
## Что внутри концепции должно быть видно
|
||||||
|
|
||||||
|
Минимум:
|
||||||
|
- Шапка профиля (имя, салон, статус)
|
||||||
|
- Список разделов меню
|
||||||
|
- Хотя бы один из подэкранов (например, «Подбор техники» — чек-лист)
|
||||||
|
|
||||||
|
Желательно:
|
||||||
|
- Светлая и тёмная тема
|
||||||
|
- Состояние active / lapsed для статуса менеджера
|
||||||
|
- Пустые состояния («Заявок пока нет»)
|
||||||
|
|
||||||
|
## Не складывать сюда
|
||||||
|
|
||||||
|
- Реальный код продакшена — он в `miniapp/`
|
||||||
|
- Скриншоты можно, но HTML предпочтительнее (легче перенести стили)
|
||||||
0
design-drafts/_css_A.css
Normal file
0
design-drafts/_css_A.css
Normal file
0
design-drafts/_css_C.css
Normal file
0
design-drafts/_css_C.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -42,42 +42,86 @@ const PODBOR_PRIORITIES = [
|
|||||||
{ key: "service", label: "Сервис и гарантия" },
|
{ key: "service", label: "Сервис и гарантия" },
|
||||||
];
|
];
|
||||||
|
|
||||||
/* Параметры по категориям: Главное (всегда видно) + Подробнее (свёрнуто) */
|
/* Параметры по категориям.
|
||||||
|
----------------------------------------------------------
|
||||||
|
Новая схема (иерархический wizard):
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
key: "install",
|
||||||
|
title: "Тип установки",
|
||||||
|
type: "single" | "multi",
|
||||||
|
options: [ { key, label, hint, star?, pict? } ]
|
||||||
|
// ИЛИ если опции зависят от предыдущего шага:
|
||||||
|
optionsBy: { dependsOn: "<prevStepKey>", map: { <prevVal>: [options] } }
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
Старая схема (legacy, без wizard):
|
||||||
|
primary: [...], features: [...]
|
||||||
|
---------------------------------------------------------- */
|
||||||
const PODBOR_PARAMS = {
|
const PODBOR_PARAMS = {
|
||||||
fridge: {
|
fridge: {
|
||||||
primary: [
|
steps: [
|
||||||
{ key: "type", label: "Тип", options: [
|
{
|
||||||
{ key: "two_chamber", label: "Двухкамерный" },
|
key: "install",
|
||||||
{ key: "sbs", label: "Side-by-side" },
|
title: "Тип установки",
|
||||||
{ key: "french", label: "French Door" },
|
type: "single",
|
||||||
{ key: "column", label: "Колонна (встр.)" },
|
options: [
|
||||||
{ key: "combi", label: "Комбинированный" },
|
{ key: "built_in", label: "Встроенный", hint: "под фасад", pict: "fridge_install_builtin" },
|
||||||
]},
|
{ key: "freestanding", label: "Отдельностоящий", hint: "соло на полу", pict: "fridge_install_freestanding" },
|
||||||
{ key: "width", label: "Ширина, см", options: [
|
],
|
||||||
{ key: "54", label: "54" }, { key: "60", label: "60" },
|
},
|
||||||
{ key: "70", label: "70" }, { key: "75", label: "75" }, { key: "91", label: "91" },
|
{
|
||||||
]},
|
key: "chamber",
|
||||||
{ key: "volume", label: "Объём, л", options: [
|
title: "Тип камеры",
|
||||||
{ key: "to300", label: "до 300" },
|
type: "single",
|
||||||
{ key: "300-450", label: "300–450" },
|
optionsBy: {
|
||||||
{ key: "450-600", label: "450–600" },
|
dependsOn: "install",
|
||||||
{ key: "600+", label: "600+" },
|
map: {
|
||||||
]},
|
built_in: [
|
||||||
{ key: "color", label: "Цвет", options: [
|
{ key: "single", label: "Однокамерный", hint: "только холод", pict: "fridge_bi_single" },
|
||||||
{ key: "white", label: "Белый" },
|
{ key: "two_chamber", label: "Двухкамерный", hint: "холод + мороз", pict: "fridge_bi_two" },
|
||||||
{ key: "inox", label: "Нерж. сталь" },
|
{ key: "col_cold", label: "Холодильная колонна", hint: "только холод · высокая", pict: "fridge_bi_colcold" },
|
||||||
{ key: "black", label: "Чёрный" },
|
{ key: "col_freeze", label: "Морозильная колонна", hint: "только мороз · высокая", pict: "fridge_bi_colfreeze" },
|
||||||
{ key: "anthracite", label: "Антрацит" },
|
{ key: "col_pair", label: "Пара колонн", hint: "холод + мороз · рядом", pict: "fridge_bi_colpair" },
|
||||||
{ key: "builtin", label: "Под фасад" },
|
],
|
||||||
]},
|
freestanding: [
|
||||||
],
|
{ key: "single", label: "Однокамерный", hint: "мини · бар", pict: "fridge_fs_single" },
|
||||||
features: [
|
{ key: "two_chamber", label: "Двухкамерный", hint: "морозилка снизу", pict: "fridge_fs_two" },
|
||||||
{ key: "nofrost", label: "NoFrost", hint: "не нужно размораживать вручную" },
|
{ key: "sbs", label: "Side-by-Side", hint: "распашной · 2 двери", pict: "fridge_fs_sbs" },
|
||||||
{ key: "inverter", label: "Инвертор", hint: "тише и экономичнее на ~30%" },
|
{ key: "french", label: "French Door", hint: "2 двери · ящик мороза", pict: "fridge_fs_french" },
|
||||||
{ key: "freshzone", label: "Зона свежести", hint: "овощи и зелень дольше хрустящие" },
|
{ key: "freezer", label: "Морозильная камера", hint: "отдельный морозильник", pict: "fridge_fs_freezer" },
|
||||||
{ key: "silent", label: "≤40 дБ", hint: "почти не слышно ночью" },
|
],
|
||||||
{ key: "smart", label: "Smart / Wi-Fi", hint: "управление с телефона" },
|
},
|
||||||
{ key: "ice", label: "Лёдогенератор", hint: "автоматически делает кубики" },
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "size",
|
||||||
|
title: "Размер",
|
||||||
|
type: "single",
|
||||||
|
options: [
|
||||||
|
{ key: "narrow", label: "Узкий", hint: "W 45–55 см" },
|
||||||
|
{ key: "standard", label: "Стандарт", hint: "W 55–60 см", star: true },
|
||||||
|
{ key: "wide", label: "Широкий", hint: "W 60–75 см" },
|
||||||
|
{ key: "xl", label: "XL", hint: "W 80–100 см · SbS / French Door" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "features",
|
||||||
|
title: "Особенности",
|
||||||
|
type: "multi",
|
||||||
|
options: [
|
||||||
|
{ key: "nofrost", label: "No Frost", hint: "не нужно размораживать" },
|
||||||
|
{ key: "inverter", label: "Inverter", hint: "тише и экономичнее" },
|
||||||
|
{ key: "freshzone", label: "Зона свежести", hint: "BioFresh / овощи дольше" },
|
||||||
|
{ key: "silent", label: "≤40 дБ", hint: "почти не слышно ночью" },
|
||||||
|
{ key: "smart", label: "Wi-Fi", hint: "управление с телефона" },
|
||||||
|
{ key: "ice", label: "Лёдогенератор", hint: "кубики автоматически" },
|
||||||
|
{ key: "wine", label: "Винная зона", hint: "" },
|
||||||
|
{ key: "dispenser", label: "Диспенсер воды", hint: "холодная вода / лёд через дверь" },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
hob: {
|
hob: {
|
||||||
|
|||||||
@ -790,3 +790,217 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.feature.on .feature-tick svg { width: 12px; height: 12px; stroke-width: 2.5; }
|
.feature.on .feature-tick svg { width: 12px; height: 12px; stroke-width: 2.5; }
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Иерархический wizard (новая схема steps[])
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.podbor-wizard { gap: var(--s4); }
|
||||||
|
|
||||||
|
.wiz-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 28px 1fr 28px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiz-header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiz-cat {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiz-progress {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiz-cat-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
color: var(--accent-2);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
.wiz-cat-icon svg { width: 22px; height: 22px; }
|
||||||
|
|
||||||
|
/* Чипы прошлых ответов */
|
||||||
|
.wiz-chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin: -4px 0 4px;
|
||||||
|
}
|
||||||
|
.wiz-chip {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent-2);
|
||||||
|
background: var(--warm);
|
||||||
|
border: 1px solid var(--accent-2);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: var(--r-pill);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s;
|
||||||
|
}
|
||||||
|
.wiz-chip:active { background: #EAD9B6; }
|
||||||
|
|
||||||
|
/* Заголовок шага */
|
||||||
|
.wiz-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.1;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 6px 0 0;
|
||||||
|
}
|
||||||
|
.wiz-multi {
|
||||||
|
font-style: normal;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Сетка карточек */
|
||||||
|
.wiz-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
@media (min-width: 540px) {
|
||||||
|
.wiz-grid { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiz-card {
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--r-card, 14px);
|
||||||
|
padding: 12px 10px 14px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.12s;
|
||||||
|
text-align: center;
|
||||||
|
font: inherit;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.wiz-card:active { background: var(--warm); transform: scale(0.98); }
|
||||||
|
.wiz-card.on {
|
||||||
|
border-color: var(--accent-2);
|
||||||
|
background: var(--warm);
|
||||||
|
box-shadow: 0 0 0 1px var(--accent-2) inset;
|
||||||
|
}
|
||||||
|
.wiz-card.star::before {
|
||||||
|
content: "⭐";
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
left: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiz-pict {
|
||||||
|
width: 72px;
|
||||||
|
height: 96px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
.wiz-pict svg { width: 100%; height: 100%; display: block; }
|
||||||
|
.wiz-pict-placeholder {
|
||||||
|
width: 72px;
|
||||||
|
height: 96px;
|
||||||
|
background: repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(107, 74, 43, 0.06) 6px, rgba(107, 74, 43, 0.06) 7px);
|
||||||
|
border: 1px dashed var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiz-label {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: 13.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--ink);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.wiz-hint {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 9.5px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
.wiz-tick {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: var(--accent-2);
|
||||||
|
color: var(--paper);
|
||||||
|
border-radius: var(--r-pill);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
.wiz-tick svg { width: 11px; height: 11px; stroke-width: 2.5; }
|
||||||
|
|
||||||
|
/* Review-экран */
|
||||||
|
.rev-list {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--r-card, 14px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.rev-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.rev-row:last-child { border-bottom: 0; }
|
||||||
|
.rev-label {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.rev-val {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ink);
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.rev-val .muted { color: var(--muted); font-weight: 400; }
|
||||||
|
|||||||
@ -192,10 +192,22 @@ const Podbor = (function () {
|
|||||||
/* ===================== Step: detail — menu + per-category sub-screen ===================== */
|
/* ===================== Step: detail — menu + per-category sub-screen ===================== */
|
||||||
|
|
||||||
function isCategoryFilled(catKey) {
|
function isCategoryFilled(catKey) {
|
||||||
const cat = state.per_cat[catKey];
|
const cs = state.per_cat[catKey];
|
||||||
if (!cat || !cat.params) return false;
|
if (!cs) return false;
|
||||||
const params = PODBOR_PARAMS[catKey]?.primary || [];
|
const config = PODBOR_PARAMS[catKey];
|
||||||
return params.every(p => cat.params[p.key]);
|
if (!config) return false;
|
||||||
|
// Новая схема: все single-шаги должны иметь ответ. Multi (features) — необязательно.
|
||||||
|
if (config.steps) {
|
||||||
|
const ans = cs.answers || {};
|
||||||
|
return config.steps.every(step => {
|
||||||
|
if (step.type === "multi") return true; // multi необязателен
|
||||||
|
return !!ans[step.key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Старая схема
|
||||||
|
if (!cs.params) return false;
|
||||||
|
const params = config.primary || [];
|
||||||
|
return params.every(p => cs.params[p.key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDetail() {
|
function renderDetail() {
|
||||||
@ -211,6 +223,9 @@ const Podbor = (function () {
|
|||||||
}
|
}
|
||||||
if (detailView !== "menu" && detailView.startsWith("cat:")) {
|
if (detailView !== "menu" && detailView.startsWith("cat:")) {
|
||||||
const catKey = detailView.slice(4);
|
const catKey = detailView.slice(4);
|
||||||
|
const config = PODBOR_PARAMS[catKey];
|
||||||
|
// Новая иерархическая схема → wizard. Старая → legacy-форма.
|
||||||
|
if (config?.steps) return renderCategoryWizard(catKey);
|
||||||
return renderCategoryDetail(catKey);
|
return renderCategoryDetail(catKey);
|
||||||
}
|
}
|
||||||
return renderDetailMenu();
|
return renderDetailMenu();
|
||||||
@ -255,18 +270,276 @@ const Podbor = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildPerCatSummary(catKey) {
|
function buildPerCatSummary(catKey) {
|
||||||
const cat = state.per_cat[catKey];
|
const cs = state.per_cat[catKey];
|
||||||
if (!cat || !cat.params) return "—";
|
if (!cs) return "—";
|
||||||
const params = PODBOR_PARAMS[catKey]?.primary || [];
|
const config = PODBOR_PARAMS[catKey];
|
||||||
|
// Новая схема
|
||||||
|
if (config?.steps) {
|
||||||
|
const ans = cs.answers || {};
|
||||||
|
const labels = [];
|
||||||
|
for (const step of config.steps) {
|
||||||
|
if (step.type === "multi") continue;
|
||||||
|
const val = ans[step.key];
|
||||||
|
if (!val) continue;
|
||||||
|
const opts = resolveStepOptions(step, ans);
|
||||||
|
const opt = opts.find(o => o.key === val);
|
||||||
|
if (opt) labels.push(opt.label);
|
||||||
|
}
|
||||||
|
return labels.join(" · ") || "—";
|
||||||
|
}
|
||||||
|
// Старая схема
|
||||||
|
if (!cs.params) return "—";
|
||||||
|
const params = config?.primary || [];
|
||||||
const labels = params
|
const labels = params
|
||||||
.map(p => {
|
.map(p => {
|
||||||
const opt = p.options.find(o => o.key === cat.params[p.key]);
|
const opt = p.options.find(o => o.key === cs.params[p.key]);
|
||||||
return opt ? opt.label : null;
|
return opt ? opt.label : null;
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
return labels.join(" · ") || "—";
|
return labels.join(" · ") || "—";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Возвращает реальный options[] для шага с учётом optionsBy */
|
||||||
|
function resolveStepOptions(step, answers) {
|
||||||
|
if (step.options) return step.options;
|
||||||
|
if (step.optionsBy) {
|
||||||
|
const depVal = answers[step.optionsBy.dependsOn];
|
||||||
|
return (step.optionsBy.map && step.optionsBy.map[depVal]) || [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== Иерархический wizard внутри категории ===================== */
|
||||||
|
|
||||||
|
function getCatState(catKey) {
|
||||||
|
const cs = state.per_cat[catKey];
|
||||||
|
if (cs && cs.answers) return cs; // уже в новой форме
|
||||||
|
// Миграция / инициализация
|
||||||
|
return { answers: {}, notes: cs?.notes || "", _step: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCatState(catKey, patch) {
|
||||||
|
const prev = getCatState(catKey);
|
||||||
|
const next = { ...prev, ...patch };
|
||||||
|
update({ per_cat: { ...state.per_cat, [catKey]: next } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCategoryWizard(catKey) {
|
||||||
|
const cat = PODBOR_CATEGORIES.find(x => x.key === catKey);
|
||||||
|
const config = PODBOR_PARAMS[catKey];
|
||||||
|
const cs = getCatState(catKey);
|
||||||
|
const stepIdx = Math.max(0, Math.min(cs._step || 0, config.steps.length));
|
||||||
|
|
||||||
|
// Финальный экран категории — обзор + заметки + кнопка "Готово"
|
||||||
|
if (stepIdx >= config.steps.length) {
|
||||||
|
return renderCategoryReview(catKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const step = config.steps[stepIdx];
|
||||||
|
const options = resolveStepOptions(step, cs.answers);
|
||||||
|
const isMulti = step.type === "multi";
|
||||||
|
const currentVal = cs.answers[step.key];
|
||||||
|
const currentArr = isMulti ? (Array.isArray(currentVal) ? currentVal : []) : null;
|
||||||
|
|
||||||
|
// Чипы прошлых ответов (single-шаги)
|
||||||
|
const prevChips = config.steps.slice(0, stepIdx)
|
||||||
|
.filter(s => s.type !== "multi")
|
||||||
|
.map(s => {
|
||||||
|
const v = cs.answers[s.key];
|
||||||
|
if (!v) return "";
|
||||||
|
const opts = resolveStepOptions(s, cs.answers);
|
||||||
|
const o = opts.find(x => x.key === v);
|
||||||
|
return o ? `<span class="wiz-chip" data-jump="${s.key}">${o.label}</span>` : "";
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
const cardsHtml = options.map(o => {
|
||||||
|
const isOn = isMulti ? currentArr.includes(o.key) : currentVal === o.key;
|
||||||
|
const pict = o.pict && PODBOR_PICTS[o.pict];
|
||||||
|
return `
|
||||||
|
<button class="wiz-card${isOn ? " on" : ""}${o.star ? " star" : ""}" data-val="${o.key}">
|
||||||
|
${pict ? `<div class="wiz-pict">${pict}</div>` : `<div class="wiz-pict wiz-pict-placeholder"></div>`}
|
||||||
|
<div class="wiz-label">${o.label}</div>
|
||||||
|
${o.hint ? `<div class="wiz-hint">${o.hint}</div>` : ""}
|
||||||
|
${isOn ? `<div class="wiz-tick">${ICONS.check}</div>` : ""}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
const stepNum = stepIdx + 1;
|
||||||
|
const stepTotal = config.steps.length;
|
||||||
|
|
||||||
|
const node = el(`
|
||||||
|
<section class="podbor-step podbor-wizard">
|
||||||
|
<header class="wiz-header">
|
||||||
|
<button class="podbor-back" aria-label="Назад">${ICONS.arrow_left}</button>
|
||||||
|
<div class="wiz-header-meta">
|
||||||
|
<div class="wiz-cat">${cat.label}</div>
|
||||||
|
<div class="wiz-progress">Шаг ${stepNum} из ${stepTotal}</div>
|
||||||
|
</div>
|
||||||
|
<div class="wiz-cat-icon">${ICONS[cat.icon] || ""}</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
${prevChips ? `<div class="wiz-chips">${prevChips}</div>` : ""}
|
||||||
|
|
||||||
|
<h3 class="wiz-title">${step.title}${isMulti ? ' <span class="wiz-multi">· можно несколько</span>' : ""}</h3>
|
||||||
|
|
||||||
|
<div class="wiz-grid">${cardsHtml}</div>
|
||||||
|
|
||||||
|
<div class="podbor-cta-row">
|
||||||
|
${stepIdx > 0
|
||||||
|
? `<button class="btn-secondary" id="wizPrev">Назад</button>`
|
||||||
|
: `<button class="btn-secondary" id="wizMenu">К списку</button>`
|
||||||
|
}
|
||||||
|
${isMulti
|
||||||
|
? `<button class="btn-primary" id="wizNext">Дальше</button>`
|
||||||
|
: (currentVal ? `<button class="btn-primary" id="wizNext">Дальше</button>` : "")
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Клик по карточке
|
||||||
|
node.querySelectorAll(".wiz-card").forEach(card => {
|
||||||
|
card.addEventListener("click", () => {
|
||||||
|
const val = card.dataset.val;
|
||||||
|
const cs2 = getCatState(catKey);
|
||||||
|
const newAns = { ...cs2.answers };
|
||||||
|
if (isMulti) {
|
||||||
|
const arr = Array.isArray(newAns[step.key]) ? newAns[step.key] : [];
|
||||||
|
newAns[step.key] = arr.includes(val) ? arr.filter(x => x !== val) : [...arr, val];
|
||||||
|
} else {
|
||||||
|
newAns[step.key] = val;
|
||||||
|
// Если меняем answer для шага, от которого зависит следующий — сбросим все последующие answers
|
||||||
|
for (let i = stepIdx + 1; i < config.steps.length; i++) {
|
||||||
|
const s = config.steps[i];
|
||||||
|
if (s.optionsBy && s.optionsBy.dependsOn === step.key) {
|
||||||
|
delete newAns[s.key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCatState(catKey, { answers: newAns });
|
||||||
|
// Single-select: автопереход на следующий шаг
|
||||||
|
if (!isMulti) {
|
||||||
|
setCatState(catKey, { _step: stepIdx + 1 });
|
||||||
|
haptic && haptic("impact");
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Чипы — клик возвращает к шагу
|
||||||
|
node.querySelectorAll(".wiz-chip[data-jump]").forEach(chip => {
|
||||||
|
chip.addEventListener("click", () => {
|
||||||
|
const targetKey = chip.dataset.jump;
|
||||||
|
const targetIdx = config.steps.findIndex(s => s.key === targetKey);
|
||||||
|
if (targetIdx >= 0) {
|
||||||
|
setCatState(catKey, { _step: targetIdx });
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Кнопки
|
||||||
|
const wizPrev = node.querySelector("#wizPrev");
|
||||||
|
if (wizPrev) wizPrev.addEventListener("click", () => {
|
||||||
|
setCatState(catKey, { _step: Math.max(0, stepIdx - 1) });
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
const wizMenu = node.querySelector("#wizMenu");
|
||||||
|
if (wizMenu) wizMenu.addEventListener("click", () => { detailView = "menu"; render(); });
|
||||||
|
const wizNext = node.querySelector("#wizNext");
|
||||||
|
if (wizNext) wizNext.addEventListener("click", () => {
|
||||||
|
setCatState(catKey, { _step: stepIdx + 1 });
|
||||||
|
haptic && haptic("impact");
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
// Header back — на предыдущий шаг или к меню
|
||||||
|
node.querySelector(".podbor-back").addEventListener("click", () => {
|
||||||
|
if (stepIdx > 0) {
|
||||||
|
setCatState(catKey, { _step: stepIdx - 1 });
|
||||||
|
render();
|
||||||
|
} else {
|
||||||
|
detailView = "menu";
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCategoryReview(catKey) {
|
||||||
|
const cat = PODBOR_CATEGORIES.find(x => x.key === catKey);
|
||||||
|
const config = PODBOR_PARAMS[catKey];
|
||||||
|
const cs = getCatState(catKey);
|
||||||
|
|
||||||
|
const rows = config.steps.map(step => {
|
||||||
|
const v = cs.answers[step.key];
|
||||||
|
const opts = resolveStepOptions(step, cs.answers);
|
||||||
|
if (step.type === "multi") {
|
||||||
|
const arr = Array.isArray(v) ? v : [];
|
||||||
|
const labels = arr.map(k => opts.find(o => o.key === k)?.label).filter(Boolean);
|
||||||
|
return `
|
||||||
|
<div class="rev-row">
|
||||||
|
<div class="rev-label">${step.title}</div>
|
||||||
|
<div class="rev-val">${labels.length ? labels.join(" · ") : '<span class="muted">не выбрано</span>'}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
const opt = opts.find(o => o.key === v);
|
||||||
|
return `
|
||||||
|
<div class="rev-row">
|
||||||
|
<div class="rev-label">${step.title}</div>
|
||||||
|
<div class="rev-val">${opt ? opt.label : '<span class="muted">—</span>'}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
const node = el(`
|
||||||
|
<section class="podbor-step podbor-wizard">
|
||||||
|
<header class="wiz-header">
|
||||||
|
<button class="podbor-back" aria-label="Назад">${ICONS.arrow_left}</button>
|
||||||
|
<div class="wiz-header-meta">
|
||||||
|
<div class="wiz-cat">${cat.label}</div>
|
||||||
|
<div class="wiz-progress">Готово</div>
|
||||||
|
</div>
|
||||||
|
<div class="wiz-cat-icon">${ICONS[cat.icon] || ""}</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<h3 class="wiz-title">Проверьте ответы</h3>
|
||||||
|
|
||||||
|
<div class="rev-list">${rows}</div>
|
||||||
|
|
||||||
|
<label class="field">
|
||||||
|
<span class="field-label">Заметки по категории</span>
|
||||||
|
<textarea data-bind="cat_notes" rows="2" placeholder="Особые пожелания клиента?">${cs.notes || ""}</textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="podbor-cta-row">
|
||||||
|
<button class="btn-secondary" id="wizEdit">Изменить</button>
|
||||||
|
<button class="btn-primary" id="wizDone">К списку категорий</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`);
|
||||||
|
node.querySelector("#wizEdit").addEventListener("click", () => {
|
||||||
|
setCatState(catKey, { _step: 0 });
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
node.querySelector("#wizDone").addEventListener("click", () => {
|
||||||
|
detailView = "menu";
|
||||||
|
haptic && haptic("success");
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
node.querySelector(".podbor-back").addEventListener("click", () => {
|
||||||
|
setCatState(catKey, { _step: config.steps.length - 1 });
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
const ta = node.querySelector("textarea[data-bind='cat_notes']");
|
||||||
|
if (ta) ta.addEventListener("input", e => {
|
||||||
|
setCatState(catKey, { notes: e.target.value });
|
||||||
|
});
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
function renderCategoryDetail(catKey) {
|
function renderCategoryDetail(catKey) {
|
||||||
const cat = PODBOR_CATEGORIES.find(x => x.key === catKey);
|
const cat = PODBOR_CATEGORIES.find(x => x.key === catKey);
|
||||||
const config = PODBOR_PARAMS[catKey];
|
const config = PODBOR_PARAMS[catKey];
|
||||||
|
|||||||
286
miniapp/assets/podbor.picts.js
Normal file
286
miniapp/assets/podbor.picts.js
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
/* ============================================================
|
||||||
|
Подбор техники — SVG-пиктограммы (стиль D · 3D перспектива)
|
||||||
|
------------------------------------------------------------
|
||||||
|
PODBOR_PICTS_DEFS — injected once into <body> on load,
|
||||||
|
содержит линейные градиенты, на которые ссылаются пиктограммы.
|
||||||
|
PODBOR_PICTS — словарь key → SVG-строка.
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
const PODBOR_PICTS_DEFS = `
|
||||||
|
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g-cold" x1="0" x2="0" y1="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#FFFFFF"/>
|
||||||
|
<stop offset="1" stop-color="#F5EDDC"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="g-freeze" x1="0" x2="0" y1="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#F0E3C8"/>
|
||||||
|
<stop offset="1" stop-color="#D8C9A8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="g-twoch" x1="0" x2="0" y1="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#FFFFFF"/>
|
||||||
|
<stop offset="0.48" stop-color="#FBF7F0"/>
|
||||||
|
<stop offset="0.52" stop-color="#F0E3C8"/>
|
||||||
|
<stop offset="1" stop-color="#D8C9A8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="g-sbs" x1="0" x2="1" y1="0" y2="0">
|
||||||
|
<stop offset="0" stop-color="#D8C9A8"/>
|
||||||
|
<stop offset="0.48" stop-color="#F0E3C8"/>
|
||||||
|
<stop offset="0.52" stop-color="#FBF7F0"/>
|
||||||
|
<stop offset="1" stop-color="#FFFFFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="g-sheen" x1="0" x2="1" y1="0" y2="0">
|
||||||
|
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.5"/>
|
||||||
|
<stop offset="0.3" stop-color="#FFFFFF" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/* Инжектим defs один раз */
|
||||||
|
(function injectPodborDefs() {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
function inject() {
|
||||||
|
if (document.getElementById("podbor-picts-defs")) return;
|
||||||
|
const wrap = document.createElement("div");
|
||||||
|
wrap.id = "podbor-picts-defs";
|
||||||
|
wrap.innerHTML = PODBOR_PICTS_DEFS;
|
||||||
|
document.body.appendChild(wrap);
|
||||||
|
}
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", inject, { once: true });
|
||||||
|
} else {
|
||||||
|
inject();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const PODBOR_PICTS = {
|
||||||
|
/* ===== Холодильник · тип установки ===== */
|
||||||
|
|
||||||
|
fridge_install_builtin: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="4" y="2" width="88" height="124" rx="4" fill="none" stroke="#6B4A2B" stroke-width="1.1" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<rect x="18" y="12" width="68" height="112" rx="4" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="14" y="8" width="68" height="112" rx="4" fill="url(#g-twoch)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="14" y="8" width="68" height="112" rx="4" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="14" y1="62" x2="82" y2="62" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<rect x="76" y="16" width="3" height="38" rx="1.5" fill="#6B4A2B"/>
|
||||||
|
<rect x="76" y="68" width="3" height="44" rx="1.5" fill="#6B4A2B"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_install_freestanding: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="18" y="12" width="68" height="108" rx="6" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-twoch)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="14" y1="60" x2="82" y2="60" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<rect x="76" y="16" width="3" height="36" rx="1.5" fill="#6B4A2B"/>
|
||||||
|
<rect x="76" y="66" width="3" height="42" rx="1.5" fill="#6B4A2B"/>
|
||||||
|
<rect x="22" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="68" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<line x1="6" y1="124" x2="90" y2="124" stroke="#6B4A2B" stroke-width="0.8" opacity="0.35"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
/* ===== Холодильник · встроенный · тип камеры ===== */
|
||||||
|
|
||||||
|
fridge_bi_single: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="4" y="2" width="88" height="124" rx="4" fill="none" stroke="#6B4A2B" stroke-width="1.1" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<rect x="18" y="12" width="68" height="112" rx="4" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="14" y="8" width="68" height="112" rx="4" fill="url(#g-cold)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="14" y="8" width="68" height="112" rx="4" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="22" y1="30" x2="74" y2="30" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="50" x2="74" y2="50" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="70" x2="74" y2="70" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="90" x2="74" y2="90" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="110" x2="74" y2="110" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<rect x="76" y="16" width="3" height="96" rx="1.5" fill="#6B4A2B"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_bi_two: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="4" y="2" width="88" height="124" rx="4" fill="none" stroke="#6B4A2B" stroke-width="1.1" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<rect x="18" y="12" width="68" height="112" rx="4" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="14" y="8" width="68" height="112" rx="4" fill="url(#g-twoch)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="14" y="8" width="68" height="112" rx="4" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="14" y1="64" x2="82" y2="64" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<line x1="22" y1="22" x2="74" y2="22" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="38" x2="74" y2="38" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="54" x2="74" y2="54" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<rect x="20" y="72" width="56" height="11" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="87" width="56" height="11" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="102" width="56" height="11" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<text x="76" y="78" font-family="JetBrains Mono" font-size="7" fill="#6B4A2B" stroke="none" text-anchor="middle">❄</text>
|
||||||
|
<rect x="78" y="14" width="2.5" height="40" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="78" y="70" width="2.5" height="46" rx="1" fill="#6B4A2B"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_bi_colcold: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="20" y="2" width="56" height="124" rx="4" fill="none" stroke="#6B4A2B" stroke-width="1.1" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<rect x="32" y="12" width="40" height="112" rx="4" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="28" y="8" width="40" height="112" rx="4" fill="url(#g-cold)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="28" y="8" width="40" height="112" rx="4" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="34" y1="22" x2="62" y2="22" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.5"/>
|
||||||
|
<line x1="34" y1="38" x2="62" y2="38" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.5"/>
|
||||||
|
<line x1="34" y1="54" x2="62" y2="54" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.5"/>
|
||||||
|
<line x1="34" y1="70" x2="62" y2="70" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.5"/>
|
||||||
|
<line x1="34" y1="86" x2="62" y2="86" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.5"/>
|
||||||
|
<line x1="34" y1="102" x2="62" y2="102" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.5"/>
|
||||||
|
<rect x="62.5" y="16" width="2.5" height="96" rx="1" fill="#6B4A2B"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_bi_colfreeze: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="20" y="2" width="56" height="124" rx="4" fill="none" stroke="#6B4A2B" stroke-width="1.1" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<rect x="32" y="12" width="40" height="112" rx="4" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="28" y="8" width="40" height="112" rx="4" fill="url(#g-freeze)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="28" y="8" width="40" height="112" rx="4" fill="url(#g-sheen)"/>
|
||||||
|
<text x="48" y="24" font-family="JetBrains Mono" font-size="9" fill="#6B4A2B" stroke="none" text-anchor="middle">❄</text>
|
||||||
|
<rect x="34" y="30" width="28" height="12" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="34" y="46" width="28" height="12" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="34" y="62" width="28" height="12" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="34" y="78" width="28" height="12" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="34" y="94" width="28" height="12" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="62.5" y="28" width="2.5" height="84" rx="1" fill="#6B4A2B"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_bi_colpair: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="2" y="2" width="92" height="124" rx="4" fill="none" stroke="#6B4A2B" stroke-width="1.1" stroke-dasharray="3 3" opacity="0.45"/>
|
||||||
|
<rect x="10" y="12" width="36" height="112" rx="3" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="50" y="12" width="36" height="112" rx="3" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="6" y="8" width="36" height="112" rx="3" fill="url(#g-cold)" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<rect x="6" y="8" width="36" height="112" rx="3" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="10" y1="20" x2="38" y2="20" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="10" y1="36" x2="38" y2="36" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="10" y1="52" x2="38" y2="52" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="10" y1="68" x2="38" y2="68" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="10" y1="84" x2="38" y2="84" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="10" y1="100" x2="38" y2="100" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<rect x="36.5" y="14" width="2" height="100" rx="0.8" fill="#6B4A2B"/>
|
||||||
|
<rect x="46" y="8" width="36" height="112" rx="3" fill="url(#g-freeze)" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<rect x="46" y="8" width="36" height="112" rx="3" fill="url(#g-sheen)"/>
|
||||||
|
<text x="64" y="20" font-family="JetBrains Mono" font-size="7" fill="#6B4A2B" stroke="none" text-anchor="middle">❄</text>
|
||||||
|
<rect x="51" y="26" width="26" height="10" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="51" y="40" width="26" height="10" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="51" y="54" width="26" height="10" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="51" y="68" width="26" height="10" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="51" y="82" width="26" height="10" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="51" y="96" width="26" height="10" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="45.5" y="14" width="2" height="100" rx="0.8" fill="#6B4A2B"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
/* ===== Холодильник · отдельностоящий · тип камеры ===== */
|
||||||
|
|
||||||
|
fridge_fs_single: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="18" y="12" width="68" height="108" rx="6" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-cold)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="22" y1="30" x2="74" y2="30" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="50" x2="74" y2="50" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="70" x2="74" y2="70" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="90" x2="74" y2="90" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<rect x="76" y="16" width="3" height="92" rx="1.5" fill="#6B4A2B"/>
|
||||||
|
<rect x="22" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="68" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<line x1="6" y1="124" x2="90" y2="124" stroke="#6B4A2B" stroke-width="0.8" opacity="0.35"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_fs_two: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="18" y="12" width="68" height="108" rx="6" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-twoch)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="14" y1="62" x2="82" y2="62" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<line x1="22" y1="22" x2="74" y2="22" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="38" x2="74" y2="38" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<line x1="22" y1="54" x2="74" y2="54" stroke="#6B4A2B" stroke-width="1" stroke-dasharray="2 3" opacity="0.45"/>
|
||||||
|
<rect x="20" y="70" width="56" height="12" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="86" width="56" height="12" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="102" width="56" height="11" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<text x="76" y="78" font-family="JetBrains Mono" font-size="7" fill="#6B4A2B" stroke="none" text-anchor="middle">❄</text>
|
||||||
|
<rect x="78" y="14" width="2.5" height="40" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="78" y="68" width="2.5" height="42" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="22" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="68" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<line x1="6" y1="124" x2="90" y2="124" stroke="#6B4A2B" stroke-width="0.8" opacity="0.35"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_fs_sbs: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="14" y="12" width="76" height="108" rx="6" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="10" y="8" width="76" height="108" rx="6" fill="url(#g-sbs)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="10" y="8" width="76" height="108" rx="6" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="48" y1="8" x2="48" y2="116" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<text x="29" y="22" font-family="JetBrains Mono" font-size="7" fill="#6B4A2B" stroke="none" text-anchor="middle">❄</text>
|
||||||
|
<rect x="16" y="28" width="28" height="11" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="16" y="42" width="28" height="11" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="16" y="56" width="28" height="11" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="16" y="70" width="28" height="11" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="16" y="84" width="28" height="11" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<rect x="16" y="98" width="28" height="10" rx="1" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1"/>
|
||||||
|
<line x1="52" y1="22" x2="82" y2="22" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="52" y1="38" x2="82" y2="38" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="52" y1="54" x2="82" y2="54" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="52" y1="70" x2="82" y2="70" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="52" y1="86" x2="82" y2="86" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="52" y1="102" x2="82" y2="102" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<rect x="42" y="16" width="2.5" height="92" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="51.5" y="16" width="2.5" height="92" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="18" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="72" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<line x1="6" y1="124" x2="90" y2="124" stroke="#6B4A2B" stroke-width="0.8" opacity="0.35"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_fs_french: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="14" y="12" width="76" height="108" rx="6" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="10" y="8" width="76" height="108" rx="6" fill="url(#g-twoch)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="10" y="8" width="76" height="108" rx="6" fill="url(#g-sheen)"/>
|
||||||
|
<line x1="10" y1="68" x2="86" y2="68" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<line x1="48" y1="8" x2="48" y2="68" stroke="#6B4A2B" stroke-width="1.4"/>
|
||||||
|
<line x1="16" y1="24" x2="42" y2="24" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="54" y1="24" x2="80" y2="24" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="16" y1="40" x2="42" y2="40" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="54" y1="40" x2="80" y2="40" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="16" y1="56" x2="42" y2="56" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<line x1="54" y1="56" x2="80" y2="56" stroke="#6B4A2B" stroke-width="0.9" stroke-dasharray="2 2" opacity="0.5"/>
|
||||||
|
<rect x="42" y="16" width="2.5" height="44" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="51.5" y="16" width="2.5" height="44" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="16" y="76" width="64" height="32" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<text x="48" y="96" font-family="JetBrains Mono" font-size="9" fill="#6B4A2B" stroke="none" text-anchor="middle">❄</text>
|
||||||
|
<rect x="18" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="72" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<line x1="6" y1="124" x2="90" y2="124" stroke="#6B4A2B" stroke-width="0.8" opacity="0.35"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
|
||||||
|
fridge_fs_freezer: `
|
||||||
|
<svg viewBox="0 0 96 128">
|
||||||
|
<rect x="18" y="12" width="68" height="108" rx="6" fill="#6B4A2B" opacity="0.1"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-freeze)" stroke="#6B4A2B" stroke-width="1.6"/>
|
||||||
|
<rect x="14" y="8" width="68" height="108" rx="6" fill="url(#g-sheen)"/>
|
||||||
|
<text x="48" y="24" font-family="JetBrains Mono" font-size="11" fill="#6B4A2B" stroke="none" text-anchor="middle">❄</text>
|
||||||
|
<rect x="20" y="32" width="56" height="14" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="50" width="56" height="14" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="68" width="56" height="14" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="86" width="56" height="14" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="20" y="104" width="56" height="8" rx="1.5" fill="#FBF7F0" stroke="#6B4A2B" stroke-width="1.1"/>
|
||||||
|
<rect x="22" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<rect x="68" y="116" width="6" height="6" rx="1" fill="#6B4A2B"/>
|
||||||
|
<line x1="6" y1="124" x2="90" y2="124" stroke="#6B4A2B" stroke-width="0.8" opacity="0.35"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
};
|
||||||
@ -12,8 +12,8 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<link rel="stylesheet" href="assets/styles.css?v=20260510a">
|
<link rel="stylesheet" href="assets/styles.css?v=20260510b">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260510a">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260510b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main id="app">
|
<main id="app">
|
||||||
@ -21,9 +21,10 @@
|
|||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script src="assets/icons.js?v=20260510a"></script>
|
<script src="assets/icons.js?v=20260510b"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260510a"></script>
|
<script src="assets/podbor.config.js?v=20260510b"></script>
|
||||||
<script src="assets/podbor.js?v=20260510a"></script>
|
<script src="assets/podbor.picts.js?v=20260510b"></script>
|
||||||
<script src="assets/app.js?v=20260510a"></script>
|
<script src="assets/podbor.js?v=20260510b"></script>
|
||||||
|
<script src="assets/app.js?v=20260510b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user