248 lines
8.5 KiB
JavaScript
248 lines
8.5 KiB
JavaScript
const PRODUCTS = {
|
||
base: [
|
||
{ id: "espresso", label: "Эспрессо", price: 900, hasMilk: false, drink: true },
|
||
{ id: "americano", label: "Американо", price: 1100, hasMilk: false, drink: true },
|
||
{ id: "latte", label: "Латте (база)", price: 900, hasMilk: true, drink: true },
|
||
{ id: "mocha", label: "Мока", price: 1100, hasMilk: true, drink: true },
|
||
{ id: "herb", label: "Травяной чай", price: 800, hasMilk: false, drink: true },
|
||
],
|
||
milk: [
|
||
{ id: "none", label: "Без молока", price: 0 },
|
||
{ id: "milk", label: "Коровье", price: 100 },
|
||
{ id: "oat", label: "Овсяное", price: 300 },
|
||
{ id: "almond", label: "Миндальное", price: 300 },
|
||
{ id: "coconut", label: "Кокосовое", price: 350 },
|
||
],
|
||
syrup: [
|
||
{ id: "none", label: "Без сиропа", price: 0 },
|
||
{ id: "vanilla", label: "Ваниль", price: 300 },
|
||
{ id: "caramel", label: "Карамель", price: 300 },
|
||
{ id: "lavender", label: "Лаванда", price: 350 },
|
||
{ id: "honey", label: "Мёд", price: 350 },
|
||
],
|
||
size: [
|
||
{ id: "s", label: "250 мл", price: 0 },
|
||
{ id: "m", label: "350 мл", price: 300 },
|
||
{ id: "l", label: "450 мл", price: 500 },
|
||
],
|
||
temp: [
|
||
{ id: "hot", label: "Горячий", price: 0 },
|
||
{ id: "iced", label: "На льду", price: 200 },
|
||
],
|
||
};
|
||
|
||
const BASE_COLORS = {
|
||
espresso: "var(--kt-ai-fg)",
|
||
americano: "var(--kt-ai-fg-muted)",
|
||
latte: "var(--kt-ai-primary-subtle)",
|
||
mocha: "var(--kt-ai-border-strong)",
|
||
herb: "var(--kt-ai-chart-green)",
|
||
};
|
||
const MILK_COLORS = {
|
||
none: "transparent",
|
||
milk: "var(--kt-ai-bg)",
|
||
oat: "var(--kt-ai-bg-soft)",
|
||
almond: "var(--kt-ai-bg-hover)",
|
||
coconut: "var(--kt-ai-bg-elevated)",
|
||
};
|
||
const SYRUP_COLORS = {
|
||
none: "transparent",
|
||
vanilla: "var(--kt-ai-chip-orange-bg)",
|
||
caramel: "var(--kt-ai-chip-orange-fg)",
|
||
lavender: "var(--kt-ai-chip-purple-bg)",
|
||
honey: "var(--kt-ai-chip-orange-bg)",
|
||
};
|
||
|
||
const MOODS = {
|
||
wake: { base: "espresso", milk: "none", syrup: "none", size: "s", temp: "hot" },
|
||
soft: { base: "latte", milk: "milk", syrup: "vanilla", size: "m", temp: "hot" },
|
||
light: { base: "herb", milk: "none", syrup: "honey", size: "m", temp: "hot" },
|
||
};
|
||
|
||
const state = { base: null, milk: "none", syrup: "none", size: "m", temp: "hot" };
|
||
|
||
const $ = (id) => document.getElementById(id);
|
||
const byId = (group, id) => (PRODUCTS[group] || []).find((p) => p.id === id);
|
||
const fmt = (price) => price.toLocaleString("ru-RU") + " \u20B8";
|
||
|
||
function renderGroup(group) {
|
||
const wrap = $("group-" + group);
|
||
wrap.innerHTML = "";
|
||
PRODUCTS[group].forEach((opt) => {
|
||
const label = document.createElement("label");
|
||
label.className = "opt";
|
||
|
||
const input = document.createElement("input");
|
||
input.type = "radio";
|
||
input.name = "opt-" + group;
|
||
input.value = opt.id;
|
||
input.checked = state[group] === opt.id;
|
||
input.addEventListener("change", () => {
|
||
state[group] = opt.id;
|
||
update();
|
||
});
|
||
|
||
const name = document.createElement("span");
|
||
name.className = "opt-label";
|
||
name.textContent = opt.label;
|
||
|
||
label.appendChild(input);
|
||
label.appendChild(name);
|
||
if (opt.price > 0) {
|
||
const price = document.createElement("span");
|
||
price.className = "opt-price";
|
||
price.textContent = "+" + fmt(opt.price);
|
||
label.appendChild(price);
|
||
}
|
||
wrap.appendChild(label);
|
||
});
|
||
}
|
||
|
||
function selected(group) {
|
||
return byId(group, state[group]);
|
||
}
|
||
|
||
function priceTotal() {
|
||
if (!state.base) return 0;
|
||
const b = selected("base");
|
||
const m = selected("milk");
|
||
const s = selected("syrup");
|
||
const z = selected("size");
|
||
const t = selected("temp");
|
||
return (
|
||
b.price +
|
||
(b.hasMilk ? m.price : 0) +
|
||
(b.drink ? s.price : 0) +
|
||
z.price +
|
||
t.price
|
||
);
|
||
}
|
||
|
||
function paintCup() {
|
||
const layers = $("layers");
|
||
const ice = $("iceGroup");
|
||
const steam = $("steam");
|
||
layers.innerHTML = "";
|
||
ice.innerHTML = "";
|
||
if (!state.base) return;
|
||
|
||
const b = selected("base");
|
||
const m = selected("milk");
|
||
const s = selected("syrup");
|
||
const z = selected("size");
|
||
const t = selected("temp");
|
||
|
||
const fillFactor = z.id === "s" ? 0.45 : z.id === "m" ? 0.72 : 0.9;
|
||
const innerTop = 52;
|
||
const innerH = 158;
|
||
const fillH = Math.round(innerH * fillFactor);
|
||
const fillY = innerTop + (innerH - fillH);
|
||
|
||
const widthBySize = { s: 88, m: 124, l: 148 };
|
||
const width = widthBySize[z.id];
|
||
const cupX = 100 - width / 2;
|
||
[ $("cupInner"), $("cupBody") ].forEach((el) => {
|
||
el.setAttribute("width", width);
|
||
el.setAttribute("x", cupX);
|
||
});
|
||
|
||
const NS = "http://www.w3.org/2000/svg";
|
||
const addRect = (x, width, y, h, color, rx) => {
|
||
const rect = document.createElementNS(NS, "rect");
|
||
rect.setAttribute("x", x);
|
||
rect.setAttribute("width", width);
|
||
rect.setAttribute("y", Math.max(innerTop, y));
|
||
rect.setAttribute("height", h);
|
||
rect.setAttribute("fill", color);
|
||
rect.setAttribute("class", "part");
|
||
if (rx) rect.setAttribute("rx", rx);
|
||
layers.appendChild(rect);
|
||
return rect;
|
||
};
|
||
const innerX = cupX + 5;
|
||
const innerW = width - 10;
|
||
let y = fillY + fillH;
|
||
|
||
if (t.id === "iced") {
|
||
if (b.hasMilk && m.id !== "none") { y -= fillH * 0.35; addRect(innerX, innerW, y, fillH * 0.35, MILK_COLORS[m.id]); }
|
||
y -= fillH * 0.45; addRect(innerX, innerW, y, fillH * 0.45, BASE_COLORS[b.id]);
|
||
if (b.drink && s.id !== "none") { y -= fillH * 0.2; addRect(innerX, innerW, y, fillH * 0.2, SYRUP_COLORS[s.id]); }
|
||
for (let i = 0; i < 3; i++) {
|
||
const cube = document.createElementNS(NS, "rect");
|
||
cube.setAttribute("x", cupX + 10 + i * (innerW - 20) / 3);
|
||
cube.setAttribute("y", fillY + 6);
|
||
cube.setAttribute("width", Math.round((innerW - 20) / 3 - 8));
|
||
cube.setAttribute("height", "22");
|
||
cube.setAttribute("rx", "4");
|
||
cube.setAttribute("fill", "rgba(190,222,240,0.9)");
|
||
cube.setAttribute("stroke", "rgba(255,255,255,0.7)");
|
||
cube.setAttribute("stroke-width", "2");
|
||
cube.setAttribute("class", "part");
|
||
ice.appendChild(cube);
|
||
}
|
||
} else {
|
||
addRect(innerX, innerW, fillY, fillH * 0.45, BASE_COLORS[b.id]);
|
||
if (b.hasMilk && m.id !== "none") addRect(innerX, innerW, fillY + fillH * 0.45, fillH * 0.3, MILK_COLORS[m.id]);
|
||
if (b.drink && s.id !== "none") addRect(innerX, innerW, fillY + fillH * 0.75, fillH * 0.25, SYRUP_COLORS[s.id]);
|
||
}
|
||
|
||
steam.setAttribute("opacity", t.id === "hot" ? "1" : "0");
|
||
}
|
||
|
||
function buildName(b, m, s, z, t) {
|
||
const tempWord = t.id === "iced" ? "Ледяной" : "Горячий";
|
||
const sizeWord = z.id === "s" ? "маленький" : z.id === "m" ? "средний" : "большой";
|
||
const bits = [tempWord, sizeWord.toLowerCase()];
|
||
if (b.hasMilk && m.id !== "none") bits.push(m.label.toLowerCase());
|
||
if (b.drink && s.id !== "none") bits.push("сироп " + s.label.toLowerCase());
|
||
bits.push(b.label.toLowerCase());
|
||
return bits.join(", ");
|
||
}
|
||
|
||
function update() {
|
||
paintCup();
|
||
if (!state.base) return;
|
||
const b = selected("base");
|
||
const m = selected("milk");
|
||
const s = selected("syrup");
|
||
const z = selected("size");
|
||
const t = selected("temp");
|
||
|
||
$("buildName").textContent = buildName(b, m, s, z, t);
|
||
|
||
const lines = ["Основа: " + b.label, "Размер: " + z.label, "Подача: " + t.label];
|
||
if (b.hasMilk) lines.splice(1, 0, "Молоко: " + m.label);
|
||
if (b.drink) lines.splice(2, 0, "Сироп: " + s.label);
|
||
$("buildSummary").innerHTML = lines.map((l) => "<span>" + l + "</span>").join("");
|
||
|
||
const total = priceTotal();
|
||
const base = b.price;
|
||
$("buildPrice").innerHTML = "<small>Итого</small> " + fmt(total) +
|
||
(total > base ? " <small>(база " + fmt(base) + " + " + fmt(total - base) + ")</small>" : "");
|
||
}
|
||
|
||
function renderAll() {
|
||
Object.keys(PRODUCTS).forEach(renderGroup);
|
||
}
|
||
|
||
function applyMood(moodId) {
|
||
const preset = MOODS[moodId];
|
||
if (!preset) return;
|
||
Object.assign(state, preset);
|
||
renderAll();
|
||
update();
|
||
document.querySelectorAll(".mood-btn").forEach((btn) => {
|
||
const active = btn.dataset.mood === moodId;
|
||
btn.style.borderColor = active ? "var(--kt-ai-primary)" : "";
|
||
btn.style.background = active ? "var(--kt-ai-primary-subtle)" : "";
|
||
});
|
||
$("builderCard").hidden = false;
|
||
$("builderCard").scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||
}
|
||
|
||
document.querySelectorAll(".mood-btn").forEach((btn) => {
|
||
btn.addEventListener("click", () => applyMood(btn.dataset.mood));
|
||
});
|
||
|
||
renderAll();
|