Калькулятор окрашивания волос для сайта салона
Считает вилку стоимости окрашивания и время визита по четырём параметрам: длина волос, тип окрашивания, тонирование и уход после. Все коэффициенты собраны в одном объекте.
<section class="vbkoq">
<div class="vbkoq-wrap">
<div class="vbkoq-head">
<h2 class="vbkoq-h2">Сколько выйдет окрашивание</h2>
<p class="vbkoq-lead">Выберите четыре параметра, и калькулятор покажет вилку цены и время, которое вы проведёте в кресле. Так проще спланировать день и не переносить встречи.</p>
</div>
<div class="vbkoq-grid">
<div class="vbkoq-panel" id="vbkoq-panel"></div>
<aside class="vbkoq-out">
<div class="vbkoq-sum">
<span>Стоимость</span>
<b id="vbkoq-price">0 ₽</b>
</div>
<div class="vbkoq-time">
<span>Времени в кресле</span>
<b id="vbkoq-time">0 мин</b>
</div>
<ul class="vbkoq-rows" id="vbkoq-rows"></ul>
<p class="vbkoq-note">Вилка учитывает густоту и исходный цвет: на тёмных волосах осветление идёт дольше и требует больше состава. Точную цену мастер называет на консультации перед работой.</p>
<a class="vbkoq-cta" href="#zapis">Записаться на консультацию</a>
</aside>
</div>
</div>
</section>
<style>
.vbkoq, .vbkoq *, .vbkoq *::before, .vbkoq *::after { box-sizing: border-box; }
.vbkoq {
--acc: #a8547a; --acc-dark: #8c4263; --ink: #2c2329; --muted: #7b6c73; --line: #ece0e4; --soft: #faf5f7; --bg: #f7f2f4;
--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, 80px) 20px;
}
.vbkoq-wrap { max-width: 1140px; margin: 0 auto; padding: 0 clamp(0px, 2vw, 40px); }
.vbkoq-head { max-width: 44em; margin-bottom: clamp(22px, 3vw, 38px); }
.vbkoq-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; }
.vbkoq-lead { font-size: 16px; line-height: 1.65; color: var(--muted); margin: 0; }
.vbkoq-grid { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, .85fr); gap: 18px; align-items: start; }
.vbkoq-panel { background: #fff; border: 1px solid var(--line); border-radius: 20px; padding: clamp(20px, 2.4vw, 30px); display: grid; gap: 24px; }
.vbkoq-group > span { display: block; font-size: 13px; color: var(--muted); margin-bottom: 11px; }
.vbkoq-opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 9px; }
.vbkoq-opt { -webkit-appearance: none; appearance: none; margin: 0; cursor: pointer; font: inherit; font-size: 16px; line-height: 1.35;
text-align: left; padding: 14px 16px; border: 1.5px solid var(--line); border-radius: 12px;
background: #fff !important; color: var(--ink) !important; transition: border-color .18s ease, background .18s ease; }
.vbkoq-opt:hover { border-color: var(--acc); }
.vbkoq-opt[aria-pressed="true"] { border-color: var(--acc); background: var(--soft) !important; font-weight: 600; }
.vbkoq-opt i { display: block; font-style: normal; font-size: 12.5px; font-weight: 400; color: var(--muted); margin-top: 4px; }
.vbkoq-out { background: #fff; border: 1px solid var(--line); border-radius: 20px; padding: clamp(20px, 2.4vw, 28px); position: sticky; top: 20px; }
.vbkoq-sum { padding-bottom: 16px; border-bottom: 1px solid var(--line); }
.vbkoq-sum span, .vbkoq-time span { display: block; font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.vbkoq-sum b { display: block; font-family: var(--fh); font-size: clamp(25px, 2.7vw, 32px); line-height: 1.1; font-weight: 700; color: var(--acc); letter-spacing: -.02em; }
.vbkoq-time { padding: 16px 0; border-bottom: 1px solid var(--line); }
.vbkoq-time b { display: block; font-family: var(--fh); font-size: 20px; font-weight: 600; }
.vbkoq-rows { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 9px; }
.vbkoq-rows li { display: flex; justify-content: space-between; gap: 14px; font-size: 13.5px; line-height: 1.45; color: var(--muted); }
.vbkoq-rows li b { color: var(--ink); font-weight: 600; white-space: nowrap; }
.vbkoq-note { margin: 18px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--muted); }
.vbkoq-cta, .vbkoq-cta:link, .vbkoq-cta:visited, .vbkoq-cta:active, .vbkoq-cta:focus { display: flex; align-items: center; justify-content: center;
min-height: 54px; margin-top: 18px; border-radius: 12px; font-size: 15.5px; font-weight: 600;
background: var(--acc); color: #fff !important; text-decoration: none !important; transition: background .18s ease; }
.vbkoq-cta:hover { background: var(--acc-dark); color: #fff !important; }
@media (max-width: 900px) {
.vbkoq-grid { grid-template-columns: minmax(0, 1fr); }
.vbkoq-out { position: static; }
}
@media (max-width: 480px) { .vbkoq-opts { grid-template-columns: minmax(0, 1fr); } }
@media (prefers-reduced-motion: reduce) { .vbkoq * { transition: none !important; } }
</style>
<script>
(function () {
/* ВСЕ КОЭФФИЦИЕНТЫ БЛОКА: цены в рублях, время в минутах */
var CFG = {
spreadDown: 0.94, /* нижняя граница вилки цены */
spreadUp: 1.18, /* верхняя граница вилки цены */
timeUp: 1.22, /* верхняя граница вилки времени */
groups: [
{ id: 'len', title: 'Длина волос', opts: [
{ n: 'Выше плеч', hint: 'каре и короче', k: 1, min: 0 },
{ n: 'До лопаток', hint: 'средняя длина', k: 1.35, min: 20 },
{ n: 'Ниже лопаток', hint: 'длинные', k: 1.75, min: 45 },
{ n: 'Ниже пояса', hint: 'очень длинные', k: 2.15, min: 80 }
] },
{ id: 'color', title: 'Тип окрашивания', opts: [
{ n: 'Корни', hint: 'отросшая часть до 3 см', price: 3200, min: 70 },
{ n: 'Один тон', hint: 'ровный цвет по всей длине', price: 4400, min: 110 },
{ n: 'Мелирование', hint: 'осветление прядями', price: 6200, min: 165 },
{ n: 'Растяжка цвета', hint: 'мягкий переход с осветлением', price: 8600, min: 215 }
] },
{ id: 'tone', title: 'Тонирование после осветления', opts: [
{ n: 'Не нужно', hint: 'оставляем базовый цвет', price: 0, min: 0 },
{ n: 'В один тон', hint: 'убрать желтизну, закрыть чешуйки', price: 1800, min: 30 },
{ n: 'Сложное', hint: 'смешанный оттенок по зонам', price: 3100, min: 50 }
] },
{ id: 'care', title: 'Уход после окрашивания', opts: [
{ n: 'Без ухода', hint: 'только сушка и укладка', price: 900, min: 25 },
{ n: 'Экспресс', hint: 'маска в четыре руки', price: 2100, min: 20 },
{ n: 'Восстановление', hint: 'два шага, для осветлённых волос', price: 4300, min: 45 }
] }
]
};
var roots = document.querySelectorAll('.vbkoq');
for (var i = 0; i < roots.length; i++) init(roots[i]);
function init(root) {
if (root.__vb) return; root.__vb = 1;
var panel = root.querySelector('#vbkoq-panel');
var priceEl = root.querySelector('#vbkoq-price'), timeEl = root.querySelector('#vbkoq-time'), rowsEl = root.querySelector('#vbkoq-rows');
var state = { len: 1, color: 1, tone: 1, care: 0 };
function money(n) { return String(Math.round(n / 100) * 100).replace(/\B(?=(\d{3})+(?!\d))/g, ' '); }
function clock(m) {
var mm = Math.round(m / 5) * 5, h = Math.floor(mm / 60), r = mm % 60;
if (h === 0) return r + ' мин';
return r === 0 ? h + ' ч' : h + ' ч ' + r + ' мин';
}
panel.innerHTML = CFG.groups.map(function (g) {
return '<div class="vbkoq-group"><span>' + g.title + '<\/span><div class="vbkoq-opts" data-g="' + g.id + '">' +
g.opts.map(function (o, i) {
return '<button type="button" class="vbkoq-opt" data-i="' + i + '" aria-pressed="' + (state[g.id] === i ? 'true' : 'false') + '">' +
o.n + '<i>' + o.hint + '<\/i><\/button>';
}).join('') + '<\/div><\/div>';
}).join('');
function pick(id) {
var g = null;
for (var j = 0; j < CFG.groups.length; j++) if (CFG.groups[j].id === id) g = CFG.groups[j];
return g.opts[state[id]];
}
function render() {
var len = pick('len'), color = pick('color'), tone = pick('tone'), care = pick('care');
var colorPrice = color.price * len.k;
var tonePrice = tone.price ? tone.price * (len.k > 1.5 ? 1.3 : 1) : 0;
var total = colorPrice + tonePrice + care.price;
var mins = color.min + len.min + tone.min + care.min;
priceEl.textContent = 'от ' + money(total * CFG.spreadDown) + ' до ' + money(total * CFG.spreadUp) + ' ₽';
timeEl.textContent = 'от ' + clock(mins) + ' до ' + clock(mins * CFG.timeUp);
var rows = [
['Окрашивание, ' + color.n.toLowerCase(), money(colorPrice) + ' ₽'],
['Тонирование', tonePrice ? money(tonePrice) + ' ₽' : 'не выбрано'],
['Уход и укладка', care.price ? money(care.price) + ' ₽' : 'входит в цену'],
['Длина', len.n.toLowerCase()]
];
rowsEl.innerHTML = rows.map(function (r) {
return '<li><span>' + r[0] + '<\/span><b>' + r[1] + '<\/b><\/li>';
}).join('');
}
panel.addEventListener('click', function (e) {
var btn = e.target.closest ? e.target.closest('.vbkoq-opt') : null;
if (!btn) return;
var box = btn.parentNode, id = box.getAttribute('data-g');
state[id] = +btn.getAttribute('data-i');
var all = box.querySelectorAll('.vbkoq-opt');
for (var k = 0; k < all.length; k++) all[k].setAttribute('aria-pressed', all[k] === btn ? 'true' : 'false');
render();
});
render();
}
})();
</script> Вставляется целиком в блок T123 «HTML-код». Внутри уже есть стили и скрипт, подключать ничего не нужно.
Как вставить блок в Tilda
- Скопируйте кодКнопка «Скопировать код» выше кладёт всё содержимое в буфер обмена.
- Добавьте блок T123В редакторе страницы: «Библиотека блоков» → «Другое» → T123 «HTML-код».
- Вставьте и сохранитеНажмите «Контент», вставьте код в поле, сохраните и обновите страницу.
- ОпубликуйтеВ превью скрипты Tilda работают не всегда — проверяйте на опубликованной странице.
Подробная инструкция с граблями — как вставить вайб-блок в Tilda.
Что менять под себя
| Где в коде | За что отвечает |
|---|---|
объект CFG в начале скрипта | цены, множители длины и время всех вариантов |
spreadDown и spreadUp | ширина вилки цены снизу и сверху |
массив groups внутри CFG | сами группы вопросов и варианты ответов |
текст в .vbkoq-note | пояснение, от чего зависит итоговая цена |
href у кнопки | якорь перехода к записи на странице |
Калькулятор окрашивания: зачем он нужен
Главный страх перед записью на окрашивание бытовой: сколько это будет стоить и на сколько часов я выпаду из дня. Калькулятор отвечает на оба вопроса сразу, ещё до звонка администратору. Вилка вместо точной цифры выглядит правдоподобно, потому что густота и исходный цвет действительно двигают итог. Под суммой идёт расшифровка по строкам, поэтому цена перестаёт выглядеть случайной.
Все числа блока лежат в объекте CFG в первых строках скрипта: цены услуг, множители длины, время каждой процедуры и границы вилки. Интерфейс собирается из этого же объекта, поэтому добавленный вариант сам появляется кнопкой в нужной группе. Кнопки крупные и работают на телефоне, размер текста в них не вызывает подзумливание на iOS. Результат считается в браузере, запросов наружу блок не делает.
Вопросы по этому блоку
-
Откройте объект CFG в начале скрипта: у каждого варианта есть поле price в рублях и поле min в минутах. Поменяйте эти числа, разметку и стили трогать не нужно.
-
Допишите объект в массив groups: идентификатор, заголовок и варианты. Кнопки отрисуются сами, а значение попадёт в расчёт после добавления слагаемого в функцию render.
-
Итог зависит от густоты волос и исходного цвета, которые видно только на консультации. Границы вилки задаются двумя коэффициентами и при желании сводятся к одной цифре.
-
Да. Код на этой странице копируется целиком, без регистрации и подписки. Указывать авторство не нужно, использовать можно в коммерческих проектах.
-
Нет. Все классы внутри имеют собственный префикс vbkoq, поэтому не пересекаются ни со стилями Tilda, ни с другими блоками. Скрипт привязан к своему элементу и запускается один раз, даже если блок вставлен дважды.
-
Раскладка перестраивается под узкие экраны, размеры заданы через clamp, поля ввода имеют шрифт 16 пикселей — iOS не будет зумить страницу при фокусе. Отдельной мобильной версии подключать не нужно.
Другие калькуляторы
Ещё блоки для салонов красоты
Собрать сайт целиком, а не по блокам
Дровосеки делают сайты на Tilda под ключ: структура, тексты, дизайн, вёрстка и вайб-блоки под задачи бизнеса.