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:
wasrusgen 2026-05-10 23:57:03 +03:00
parent cd5d92ea17
commit 17b112f061
13 changed files with 2332 additions and 48 deletions

71
.tmp_ssh.py Normal file
View 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)

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
View 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
View File

0
design-drafts/_css_C.css Normal file
View File

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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: "300450" }, optionsBy: {
{ key: "450-600", label: "450600" }, 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 4555 см" },
{ key: "standard", label: "Стандарт", hint: "W 5560 см", star: true },
{ key: "wide", label: "Широкий", hint: "W 6075 см" },
{ key: "xl", label: "XL", hint: "W 80100 см · 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: {

View File

@ -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; }

View File

@ -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];

View 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>
`,
};

View File

@ -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>