Калькулятор кейтеринга для сайта на Tilda
Считает бюджет мероприятия по числу гостей, формату и длительности, показывает цену на одного человека и раскладывает итог на еду, посуду, работу официантов и логистику.
<section class="vbkkt">
<div class="vbkkt-wrap">
<div class="vbkkt-head">
<h2 class="vbkkt-h2">Прикиньте бюджет банкета</h2>
<p class="vbkkt-lead">Задайте число гостей, формат и длительность, и калькулятор соберёт смету и покажет, во сколько выйдет один гость.</p>
</div>
<div class="vbkkt-grid">
<div class="vbkkt-panel">
<div class="vbkkt-field">
<div class="vbkkt-lbl"><span>Гостей</span><b id="vbkkt-gnum">60</b></div>
<input type="range" id="vbkkt-guests" min="10" max="300" step="5" value="60" aria-label="Количество гостей">
<div class="vbkkt-ticks"><i>10</i><i>150</i><i>300</i></div>
</div>
<div class="vbkkt-field">
<span class="vbkkt-cap">Формат</span>
<div class="vbkkt-opts" id="vbkkt-fmt" role="group" aria-label="Формат мероприятия"></div>
</div>
<div class="vbkkt-field">
<span class="vbkkt-cap">Длительность</span>
<div class="vbkkt-opts vbkkt-opts--row" id="vbkkt-hours" role="group" aria-label="Длительность мероприятия"></div>
</div>
<div class="vbkkt-field">
<span class="vbkkt-cap">Что добавить</span>
<div class="vbkkt-opts" id="vbkkt-extra" role="group" aria-label="Дополнительные услуги"></div>
</div>
</div>
<aside class="vbkkt-out">
<div class="vbkkt-sum">
<span>Бюджет мероприятия</span>
<b id="vbkkt-total">0 ₽</b>
</div>
<div class="vbkkt-per">
<span>На одного гостя</span>
<b id="vbkkt-per">0 ₽</b>
</div>
<div class="vbkkt-bar" id="vbkkt-bar" aria-hidden="true"></div>
<ul class="vbkkt-rows" id="vbkkt-rows"></ul>
<p class="vbkkt-note">Расчёт ориентировочный: итог зависит от меню, площадки и дня недели. Смету с раскладкой по блюдам присылаем после короткого разговора о поводе и вкусах гостей.</p>
<a class="vbkkt-cta" href="#zayavka">Обсудить меню</a>
</aside>
</div>
</div>
</section>
<style>
.vbkkt, .vbkkt *, .vbkkt *::before, .vbkkt *::after { box-sizing: border-box; }
.vbkkt {
--acc: #8a5a2b; --acc-dark: #6f4720; --ink: #24201b; --muted: #6d645a; --line: #e5ddd1; --soft: #faf6ef; --bg: #f6f1e8;
--c1: #8a5a2b; --c2: #c09155; --c3: #6f7d5a; --c4: #b9ac97;
--f: var(--t-text-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif);
--fh: var(--t-headline-font, var(--f));
background: var(--bg); color: var(--ink); font-family: var(--f); padding: clamp(36px, 5vw, 82px) 20px;
}
.vbkkt-wrap { max-width: 1160px; margin: 0 auto; padding: 0 clamp(0px, 2vw, 40px); }
.vbkkt-head { max-width: 44em; margin-bottom: clamp(22px, 3vw, 36px); }
.vbkkt-h2 { font-family: var(--fh); font-size: clamp(26px, 3.4vw, 40px); line-height: 1.12; font-weight: 700; letter-spacing: -.02em; margin: 0 0 12px; }
.vbkkt-lead { font-size: 16px; line-height: 1.65; color: var(--muted); margin: 0; }
.vbkkt-grid { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, .9fr); gap: 18px; align-items: start; }
.vbkkt-panel { background: #fff; border: 1px solid var(--line); border-radius: 22px; padding: clamp(20px, 2.4vw, 30px); display: grid; gap: 26px; }
.vbkkt-lbl { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; font-size: 13px; color: var(--muted); }
.vbkkt-lbl b { font-family: var(--fh); font-size: 24px; color: var(--ink); }
.vbkkt-cap { display: block; font-size: 13px; color: var(--muted); margin-bottom: 11px; }
.vbkkt input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 4px; border-radius: 4px; background: var(--line); outline: none; }
.vbkkt input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 24px; height: 24px; border-radius: 50%;
background: var(--acc); border: 4px solid #fff; box-shadow: 0 1px 6px rgba(36,32,27,.25); cursor: pointer; }
.vbkkt input[type=range]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--acc); border: 3px solid #fff; cursor: pointer; }
.vbkkt-ticks { display: flex; justify-content: space-between; margin-top: 7px; font-size: 11px; color: #a89e91; }
.vbkkt-ticks i { font-style: normal; }
.vbkkt-opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(165px, 1fr)); gap: 9px; }
.vbkkt-opts--row { grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); }
.vbkkt-opt { -webkit-appearance: none; appearance: none; margin: 0; cursor: pointer; font: inherit; font-size: 16px; line-height: 1.3; text-align: left;
padding: 13px 15px; border: 1.5px solid var(--line); border-radius: 13px; background: #fff !important; color: var(--ink) !important;
transition: border-color .18s ease, background .18s ease; }
.vbkkt-opt:hover { border-color: var(--acc); }
.vbkkt-opt[aria-pressed="true"] { border-color: var(--acc); background: var(--soft) !important; font-weight: 600; }
.vbkkt-opt i { display: block; font-style: normal; font-size: 12.5px; font-weight: 400; color: var(--muted); margin-top: 4px; }
.vbkkt-opts--row .vbkkt-opt { text-align: center; }
.vbkkt-out { background: #fff; border: 1px solid var(--line); border-radius: 22px; padding: clamp(20px, 2.4vw, 28px); position: sticky; top: 20px; }
.vbkkt-sum span, .vbkkt-per span { display: block; font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.vbkkt-sum b { display: block; font-family: var(--fh); font-size: clamp(26px, 2.9vw, 34px); line-height: 1.05; font-weight: 700; color: var(--acc); letter-spacing: -.02em; }
.vbkkt-per { padding-top: 16px; margin-top: 16px; border-top: 1px solid var(--line); }
.vbkkt-per b { display: block; font-family: var(--fh); font-size: 21px; font-weight: 600; }
.vbkkt-bar { display: flex; height: 8px; border-radius: 8px; overflow: hidden; background: var(--soft); margin: 18px 0 16px; }
.vbkkt-bar i { display: block; height: 100%; width: 0; transition: width .4s cubic-bezier(.22,1,.36,1); }
.vbkkt-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.vbkkt-rows li { display: flex; align-items: flex-start; gap: 10px; font-size: 13.5px; line-height: 1.45; color: var(--muted); }
.vbkkt-rows em { flex: 0 0 9px; width: 9px; height: 9px; border-radius: 3px; margin-top: 4px; }
.vbkkt-rows span { flex: 1; }
.vbkkt-rows span i { display: block; font-style: normal; font-size: 11.5px; color: #a89e91; margin-top: 2px; }
.vbkkt-rows b { color: var(--ink); font-weight: 600; white-space: nowrap; }
.vbkkt-note { margin: 18px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--muted); }
.vbkkt-cta, .vbkkt-cta:link, .vbkkt-cta:visited { display: block; margin-top: 16px; text-align: center; text-decoration: none;
background: var(--acc) !important; color: #fff !important; font-size: 15px; font-weight: 600; border-radius: 12px; padding: 15px 18px;
transition: background .18s ease; }
.vbkkt-cta:hover { background: var(--acc-dark) !important; }
@media (max-width: 900px) { .vbkkt-grid { grid-template-columns: minmax(0, 1fr); } .vbkkt-out { position: static; } }
@media (prefers-reduced-motion: reduce) { .vbkkt-bar i, .vbkkt-opt, .vbkkt-cta { transition: none !important; } }
</style>
<script>
(function () {
/* ── ТАРИФЫ И КОЭФФИЦИЕНТЫ: правьте только этот объект ── */
var CFG = {
formats: [
{ id: 'furshet', name: 'Фуршет', hint: 'канапе и закуски, гости стоят', per: 1850 },
{ id: 'banket', name: 'Банкет', hint: 'посадка, горячее и салаты', per: 3200 },
{ id: 'coffee', name: 'Кофе-брейк', hint: 'выпечка, кофе, чай', per: 780 }
],
hours: [
{ h: 2, k: 1 },
{ h: 3, k: 1.14 },
{ h: 4, k: 1.28 },
{ h: 6, k: 1.5 }
],
dishes: 290, /* аренда посуды и текстиля на гостя, ₽ */
waiterGuests: 20, /* сколько гостей на одного официанта */
waiterShift: 4200, /* смена официанта до 4 часов, ₽ */
waiterExtra: 900, /* доплата за каждый час сверх четырёх, ₽ */
logisticBase: 4500, /* подача, сборка и вывоз, ₽ */
logisticGuest: 45 /* та же статья, добавка на гостя, ₽ */
};
var roots = document.querySelectorAll('.vbkkt');
for (var i = 0; i < roots.length; i++) init(roots[i]);
function init(root) {
if (root.__vb) return; root.__vb = 1;
var $ = function (s) { return root.querySelector(s); };
var st = { guests: 60, format: 'furshet', hours: 3, dishes: true, waiters: true };
function fmt(n) { return String(Math.round(n / 10) * 10).replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + ' ₽'; }
function guestWord(n) { var m = n % 100, k = n % 10; return (m > 10 && m < 20) || k === 0 || k > 4 ? 'гостей' : k === 1 ? 'гость' : 'гостя'; }
function hourWord(n) { return n === 1 ? 'час' : n < 5 ? 'часа' : 'часов'; }
$('#vbkkt-fmt').innerHTML = CFG.formats.map(function (f) {
return '<button type="button" class="vbkkt-opt" data-id="' + f.id + '" aria-pressed="' + (f.id === st.format) + '">' +
f.name + '<i>' + f.hint + ', ' + f.per + ' ₽ с гостя<\/i><\/button>';
}).join('');
$('#vbkkt-hours').innerHTML = CFG.hours.map(function (o) {
return '<button type="button" class="vbkkt-opt" data-h="' + o.h + '" aria-pressed="' + (o.h === st.hours) + '">' +
o.h + ' ' + hourWord(o.h) + '<\/button>';
}).join('');
$('#vbkkt-extra').innerHTML =
'<button type="button" class="vbkkt-opt" data-x="dishes" aria-pressed="true">Посуда и текстиль<i>тарелки, приборы, скатерти<\/i><\/button>' +
'<button type="button" class="vbkkt-opt" data-x="waiters" aria-pressed="true">Официанты<i>один человек на ' + CFG.waiterGuests + ' гостей<\/i><\/button>';
function press(box, el) {
var all = box.querySelectorAll('.vbkkt-opt');
for (var k = 0; k < all.length; k++) all[k].setAttribute('aria-pressed', String(all[k] === el));
}
$('#vbkkt-fmt').addEventListener('click', function (e) {
var b = e.target.closest('.vbkkt-opt'); if (!b) return;
st.format = b.getAttribute('data-id'); press(this, b); render();
});
$('#vbkkt-hours').addEventListener('click', function (e) {
var b = e.target.closest('.vbkkt-opt'); if (!b) return;
st.hours = +b.getAttribute('data-h'); press(this, b); render();
});
$('#vbkkt-extra').addEventListener('click', function (e) {
var b = e.target.closest('.vbkkt-opt'); if (!b) return;
var key = b.getAttribute('data-x');
st[key] = !st[key];
b.setAttribute('aria-pressed', String(st[key]));
render();
});
$('#vbkkt-guests').addEventListener('input', function () { st.guests = +this.value; render(); });
function render() {
var f = CFG.formats.filter(function (o) { return o.id === st.format; })[0];
var hk = CFG.hours.filter(function (o) { return o.h === st.hours; })[0].k;
var rows = [];
var food = f.per * st.guests * hk;
rows.push({ n: 'Еда и напитки', d: f.name + ', ' + st.hours + ' ' + hourWord(st.hours), v: food, c: 'var(--c1)' });
if (st.dishes) {
var dish = CFG.dishes * st.guests;
rows.push({ n: 'Посуда и текстиль', d: CFG.dishes + ' ₽ на гостя', v: dish, c: 'var(--c2)' });
}
if (st.waiters) {
var cnt = Math.max(1, Math.ceil(st.guests / CFG.waiterGuests));
var extra = Math.max(0, st.hours - 4) * CFG.waiterExtra;
var wage = cnt * (CFG.waiterShift + extra);
rows.push({ n: 'Работа официантов', d: cnt + ' человек в смене', v: wage, c: 'var(--c3)' });
}
var log = CFG.logisticBase + CFG.logisticGuest * st.guests;
rows.push({ n: 'Подача и вывоз', d: 'доставка, сборка стола, уборка', v: log, c: 'var(--c4)' });
var total = rows.reduce(function (s, r) { return s + r.v; }, 0);
$('#vbkkt-gnum').textContent = st.guests + ' ' + guestWord(st.guests);
$('#vbkkt-total').textContent = fmt(total);
$('#vbkkt-per').textContent = fmt(total / st.guests);
$('#vbkkt-bar').innerHTML = rows.map(function (r) {
return '<i style="background:' + r.c + '; width:' + (r.v / total * 100).toFixed(1) + '%"><\/i>';
}).join('');
$('#vbkkt-rows').innerHTML = rows.map(function (r) {
return '<li><em style="background:' + r.c + '"><\/em><span>' + r.n + '<i>' + r.d + '<\/i><\/span><b>' + fmt(r.v) + '<\/b><\/li>';
}).join('');
}
render();
}
})();
</script> Вставляется целиком в блок T123 «HTML-код». Внутри уже есть стили и скрипт, подключать ничего не нужно.
Как вставить блок в Tilda
- Скопируйте кодКнопка «Скопировать код» выше кладёт всё содержимое в буфер обмена.
- Добавьте блок T123В редакторе страницы: «Библиотека блоков» → «Другое» → T123 «HTML-код».
- Вставьте и сохранитеНажмите «Контент», вставьте код в поле, сохраните и обновите страницу.
- ОпубликуйтеВ превью скрипты Tilda работают не всегда — проверяйте на опубликованной странице.
Подробная инструкция с граблями — как вставить вайб-блок в Tilda.
Что менять под себя
| Где в коде | За что отвечает |
|---|---|
объект CFG в начале скрипта | цены форматов на гостя, поправки за длительность, ставки посуды и официантов |
поля logisticBase и logisticGuest | стоимость подачи, сборки и вывоза: фиксированная часть и добавка на гостя |
атрибуты min, max и value у ползунка | границы числа гостей и значение по умолчанию |
--c1, --c2, --c3, --c4 на корне | цвета сегментов полосы и точек в списке статей |
ссылка «Обсудить меню» | адрес якоря к форме заявки на вашей странице |
Калькулятор кейтеринга: зачем он нужен
Первый вопрос заказчика банкета звучит одинаково: во сколько это обойдётся на человека. Пока ответа нет, разговор стоит на месте, и менеджер тратит время на переписку ради одной цифры. Калькулятор отвечает за него: посетитель двигает число гостей, выбирает формат и сразу видит вилку бюджета. В заявку такой человек приходит уже с ожиданиями, близкими к вашему прайсу.
Итог складывается из четырёх статей, и каждая видна отдельной строкой с пояснением, откуда взялась сумма. Полоса над списком показывает доли статей в бюджете, поэтому заметно, что половину суммы держит еда, а официанты стоят меньше, чем кажется. Все ставки лежат в объекте CFG в начале скрипта: цена формата на гостя, поправка на длительность, аренда посуды, смена официанта и логистика. Отправки данных наружу нет, вся арифметика идёт в браузере, кнопка внизу ведёт к вашей форме на странице.
Вопросы по этому блоку
-
Допишите объект в массив formats внутри CFG: название, короткое пояснение и цену на гостя. Кнопка появится сама, разметку трогать не нужно.
-
Итоговая сумма делится на текущее число гостей, включая посуду, официантов и логистику. Поэтому при маленьких заказах цена на человека выше: фиксированная часть логистики распределяется на меньшее число людей.
-
Нет, форм внутри блока нет. Кнопка ведёт к форме на странице, а результат посетитель называет менеджеру или переносит в поле комментария.
-
Да. Код на этой странице копируется целиком, без регистрации и подписки. Указывать авторство не нужно, использовать можно в коммерческих проектах.
-
Нет. Все классы внутри имеют собственный префикс vbkkt, поэтому не пересекаются ни со стилями Tilda, ни с другими блоками. Скрипт привязан к своему элементу и запускается один раз, даже если блок вставлен дважды.
-
Раскладка перестраивается под узкие экраны, размеры заданы через clamp, поля ввода имеют шрифт 16 пикселей — iOS не будет зумить страницу при фокусе. Отдельной мобильной версии подключать не нужно.
Другие калькуляторы
Ещё блоки для ресторанов и доставки
Собрать сайт целиком, а не по блокам
Дровосеки делают сайты на Tilda под ключ: структура, тексты, дизайн, вёрстка и вайб-блоки под задачи бизнеса.