Задать вопрос директору
Telegram
VK
Звонок

Блок отзывов гостей для сайта ресторана

Слайдер с отзывами и оценками по трём параметрам: кухня, обслуживание, атмосфера. Внизу — сводный балл по всем отзывам, он считается сам.

Живое демо — блок работает прямо здесь

Код блока

Скачать файлом
<section class="vbogr">
  <div class="vbogr-wrap">
    <div class="vbogr-head">
      <div>
        <h2 class="vbogr-h2">Отзывы гостей</h2>
        <p class="vbogr-lead">Собираем через QR-код на счёте. Оценка средняя по всем отзывам, а не по избранным.</p>
      </div>
      <div class="vbogr-avg">
        <b id="vbogr-avg"></b>
        <span id="vbogr-cnt"></span>
      </div>
    </div>

    <div class="vbogr-view">
      <div class="vbogr-track" id="vbogr-track"></div>
    </div>

    <div class="vbogr-nav">
      <button type="button" class="vbogr-arrow" data-dir="-1" aria-label="Предыдущий отзыв">
        <svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M12 4l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
      </button>
      <div class="vbogr-dots" id="vbogr-dots"></div>
      <button type="button" class="vbogr-arrow" data-dir="1" aria-label="Следующий отзыв">
        <svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M8 4l6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
      </button>
    </div>
  </div>
</section>

<style>
.vbogr, .vbogr *, .vbogr *::before, .vbogr *::after { box-sizing: border-box; }
.vbogr {
  --acc: #8a5a2b; --ink: #24201b; --muted: #6d645a; --line: #e5ddd1; --soft: #faf6ef; --star: #d29c3d;
  --f: var(--t-text-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif);
  --fh: var(--t-headline-font, var(--f));
  background: var(--soft); color: var(--ink); font-family: var(--f); padding: clamp(36px, 5vw, 80px) 20px; overflow: hidden;
}
.vbogr-wrap { max-width: 1240px; margin: 0 auto; padding: 0 clamp(0px, 2vw, 40px); }
.vbogr-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: clamp(22px, 2.8vw, 34px); }
.vbogr-h2 { font-family: var(--fh); font-size: clamp(26px, 3.4vw, 42px); line-height: 1.1; font-weight: 700; letter-spacing: -.025em; margin: 0 0 10px; }
.vbogr-lead { font-size: 16px; line-height: 1.6; color: var(--muted); margin: 0; max-width: 36em; }
.vbogr-avg { flex: 0 0 auto; text-align: right; }
.vbogr-avg b { display: block; font-family: var(--fh); font-size: clamp(34px, 4vw, 52px); line-height: 1; font-weight: 700; color: var(--acc); }
.vbogr-avg span { display: block; font-size: 12.5px; color: var(--muted); margin-top: 6px; }

.vbogr-view { overflow: hidden; margin: 0 -8px; }
.vbogr-track { display: flex; transition: transform .45s cubic-bezier(.22,1,.36,1); will-change: transform; }
.vbogr-slide { flex: 0 0 100%; padding: 0 8px; }
.vbogr-card { background: #fff; border: 1px solid var(--line); border-radius: 24px; padding: clamp(22px, 2.8vw, 36px);
  display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, .6fr); gap: clamp(20px, 3vw, 40px); align-items: center; }
.vbogr-dish { display: inline-block; font-size: 12.5px; font-weight: 600; color: var(--acc); background: var(--soft);
  border-radius: 999px; padding: 6px 14px; margin-bottom: 14px; }
.vbogr-txt { margin: 0 0 18px; font-family: var(--fh); font-size: clamp(16px, 1.6vw, 20px); line-height: 1.55; }
.vbogr-who { display: flex; align-items: center; gap: 12px; }
.vbogr-ava { flex: 0 0 42px; width: 42px; height: 42px; border-radius: 50%; background: var(--soft); display: flex; align-items: center;
  justify-content: center; font-size: 15px; font-weight: 700; color: var(--acc); }
.vbogr-name { display: block; font-size: 15px; font-weight: 600; }
.vbogr-date { display: block; font-size: 12.5px; color: var(--muted); margin-top: 2px; }

.vbogr-rates { display: grid; gap: 14px; }
.vbogr-rate span { display: flex; justify-content: space-between; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.vbogr-rate span b { color: var(--ink); font-weight: 600; }
.vbogr-bar { height: 5px; border-radius: 5px; background: var(--soft); overflow: hidden; }
.vbogr-bar i { display: block; height: 100%; border-radius: 5px; background: var(--star); transition: width .5s cubic-bezier(.22,1,.36,1); }

.vbogr-nav { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 22px; }
.vbogr-arrow { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; border: 1.5px solid var(--line);
  margin: 0; -webkit-appearance: none; appearance: none; cursor: pointer; border-radius: 50%; background: #fff !important; color: var(--ink) !important; transition: .18s ease; }
.vbogr-arrow:hover { border-color: var(--acc); color: var(--acc) !important; }
.vbogr-arrow svg { width: 20px; height: 20px; }
.vbogr-dots { display: flex; gap: 7px; }
.vbogr-dot { width: 8px; height: 8px; padding: 0; border: 0; margin: 0; -webkit-appearance: none; appearance: none; cursor: pointer;
  border-radius: 50%; background: #d9cdbb !important; transition: .2s ease; }
.vbogr-dot.is-on { background: var(--acc) !important; width: 24px; border-radius: 999px; }

@media (max-width: 720px) {
  .vbogr-head { flex-direction: column; align-items: flex-start; }
  .vbogr-avg { text-align: left; }
  .vbogr-card { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) { .vbogr-track, .vbogr-bar i { transition: none !important; } }
</style>

<script>
(function () {
  /* ── ОТЗЫВЫ: три оценки — кухня, обслуживание, атмосфера ── */
  var REVIEWS = [
    { name: 'Дарья', dish: 'Томлёная говядина с полбой', date: '12 марта',
      text: 'Мясо режется ложкой, полба не разварена в кашу — редкое сочетание. Официант сам предложил вино к блюду и не стал продавать самое дорогое из карты.',
      r: { kitchen: 5, service: 5, mood: 5 } },
    { name: 'Никита', dish: 'Дегустационный сет из шести подач', date: '28 февраля',
      text: 'Брали сет на годовщину. Шеф вышел объяснить две подачи, которые вызвали вопросы. Не шоу ради шоу — реально объяснил, из чего и почему.',
      r: { kitchen: 5, service: 5, mood: 4 } },
    { name: 'Алёна', dish: 'Паста с креветками', date: '5 марта',
      text: 'Паста хорошая, но ждали сорок минут в будний вечер при полупустом зале. Предупредили бы сразу — вопросов не было бы.',
      r: { kitchen: 4, service: 3, mood: 5 } },
    { name: 'Григорий', dish: 'Утка с грушей', date: '19 февраля',
      text: 'Утка с чёткой корочкой и розовым внутри, как и договаривались. Порция честная, доедал с трудом. Счёт совпал с ожиданиями по меню, без сюрпризов.',
      r: { kitchen: 5, service: 4, mood: 5 } },
    { name: 'Вера', dish: 'Завтрак: сырники и какао', date: '2 марта',
      text: 'Пришла с ребёнком в десять утра. Принесли раскраску, не сказав ни слова, — просто поняли ситуацию. Сырники не резиновые, какао не из пакетика.',
      r: { kitchen: 5, service: 5, mood: 5 } }
  ];
  var AUTOPLAY = 6000;   /* интервал автопрокрутки, 0 — выключить */
  var LABELS = { kitchen: 'Кухня', service: 'Обслуживание', mood: 'Атмосфера' };

  var roots = document.querySelectorAll('.vbogr');
  for (var i = 0; i < roots.length; i++) init(roots[i]);

  function init(root) {
    if (root.__vb) return; root.__vb = 1;
    var track = root.querySelector('#vbogr-track'), dotsBox = root.querySelector('#vbogr-dots');
    var pos = 0, timer = null;

    function word(n) { var m = n % 100, k = n % 10; return m > 10 && m < 20 ? 'отзывов' : k === 1 ? 'отзыв' : k > 1 && k < 5 ? 'отзыва' : 'отзывов'; }

    var all = REVIEWS.reduce(function (s, r) { return s + (r.r.kitchen + r.r.service + r.r.mood) / 3; }, 0) / REVIEWS.length;
    root.querySelector('#vbogr-avg').textContent = all.toFixed(1).replace('.', ',');
    root.querySelector('#vbogr-cnt').textContent = 'по ' + REVIEWS.length + ' ' + word(REVIEWS.length);

    track.innerHTML = REVIEWS.map(function (r) {
      var rates = Object.keys(LABELS).map(function (k) {
        return '<div class="vbogr-rate"><span>' + LABELS[k] + '<b>' + r.r[k] + ',0<\/b><\/span>' +
          '<div class="vbogr-bar"><i style="width:' + (r.r[k] / 5 * 100) + '%"><\/i><\/div><\/div>';
      }).join('');
      return '<div class="vbogr-slide"><article class="vbogr-card"><div>' +
        '<span class="vbogr-dish">' + r.dish + '<\/span>' +
        '<p class="vbogr-txt">' + r.text + '<\/p>' +
        '<div class="vbogr-who"><span class="vbogr-ava">' + r.name.charAt(0) + '<\/span>' +
        '<span><b class="vbogr-name">' + r.name + '<\/b><span class="vbogr-date">' + r.date + '<\/span><\/span><\/div>' +
        '<\/div><div class="vbogr-rates">' + rates + '<\/div><\/article><\/div>';
    }).join('');

    dotsBox.innerHTML = REVIEWS.map(function (_, i) {
      return '<button type="button" class="vbogr-dot' + (i === 0 ? ' is-on' : '') + '" aria-label="Отзыв ' + (i + 1) + '"><\/button>';
    }).join('');
    dotsBox.querySelectorAll('.vbogr-dot').forEach(function (d, i) {
      d.addEventListener('click', function () { pos = i; move(); restart(); });
    });

    function move() {
      track.style.transform = 'translateX(' + (-pos * 100) + '%)';
      dotsBox.querySelectorAll('.vbogr-dot').forEach(function (d, i) { d.classList.toggle('is-on', i === pos); });
    }
    function step(dir) { pos = (pos + dir + REVIEWS.length) % REVIEWS.length; move(); }

    root.querySelectorAll('.vbogr-arrow').forEach(function (b) {
      b.addEventListener('click', function () { step(+b.dataset.dir); restart(); });
    });

    var x0 = null;
    track.addEventListener('touchstart', function (e) { x0 = e.touches[0].clientX; stop(); }, { passive: true });
    track.addEventListener('touchend', function (e) {
      if (x0 === null) return;
      var dx = e.changedTouches[0].clientX - x0;
      if (Math.abs(dx) > 40) step(dx < 0 ? 1 : -1);
      x0 = null; restart();
    }, { passive: true });

    function play() { if (AUTOPLAY > 0) timer = setInterval(function () { step(1); }, AUTOPLAY); }
    function stop() { if (timer) { clearInterval(timer); timer = null; } }
    function restart() { stop(); play(); }
    root.addEventListener('mouseenter', stop);
    root.addEventListener('mouseleave', play);
    root.addEventListener('focusin', stop);

    move();
    play();
  }
})();
</script>

Вставляется целиком в блок T123 «HTML-код». Внутри уже есть стили и скрипт, подключать ничего не нужно.

Как вставить блок в Tilda

  1. Скопируйте кодКнопка «Скопировать код» выше кладёт всё содержимое в буфер обмена.
  2. Добавьте блок T123В редакторе страницы: «Библиотека блоков» → «Другое» → T123 «HTML-код».
  3. Вставьте и сохранитеНажмите «Контент», вставьте код в поле, сохраните и обновите страницу.
  4. ОпубликуйтеВ превью скрипты Tilda работают не всегда — проверяйте на опубликованной странице.

Подробная инструкция с граблями — как вставить вайб-блок в Tilda.

Что менять под себя

Где в кодеЗа что отвечает
массив REVIEWSимя, блюдо, текст, три оценки
AUTOPLAYинтервал автопрокрутки, 0 — выключить
--accцвет оценок и активной точки

Отзывы гостей ресторана: зачем он нужен

Ресторанный отзыв ценен упоминанием конкретного блюда. Карточка построена вокруг этого: есть поле «что заказывали», и оно заполняется в первую очередь, а общие слова идут следом.

Сводный балл считается по всем отзывам в массиве, а не проставляется руками. Добавили отзыв — средняя оценка обновилась, расхождений между цифрой и карточками не будет.

Вопросы по этому блоку

  • Да, как среднее по всем отзывам в массиве, с округлением до десятой. Вручную его задавать не нужно и нельзя — так исключается расхождение с карточками.

  • Да, удалите блок с оценками из шаблона карточки и строку подсчёта среднего. Слайдер продолжит работать.

  • Да. Код на этой странице копируется целиком, без регистрации и подписки. Указывать авторство не нужно, использовать можно в коммерческих проектах.

  • Нет. Все классы внутри имеют собственный префикс vbogr, поэтому не пересекаются ни со стилями Tilda, ни с другими блоками. Скрипт привязан к своему элементу и запускается один раз, даже если блок вставлен дважды.

  • Раскладка перестраивается под узкие экраны, размеры заданы через clamp, поля ввода имеют шрифт 16 пикселей — iOS не будет зумить страницу при фокусе. Отдельной мобильной версии подключать не нужно.

Другие блоки отзывов

Ещё блоки для ресторанов и доставки

Собрать сайт целиком, а не по блокам

Дровосеки делают сайты на Tilda под ключ: структура, тексты, дизайн, вёрстка и вайб-блоки под задачи бизнеса.