Внешние меню — Шаблоны монитора
Источник
BR 4.1 §5.1 Бизнес-спека: Внешние меню · tv_screen
Спецификация рендера для канала tv_screen — рекламный монитор над кассой. Открывается в Chromium kiosk-mode на мини-ПК или встроенном SoC телевизора. Описывает 3 готовых шаблона + общие технические детали (live-обновление, адаптивность, offline ZIP).
Целевое железо и установка
Железо
- Min: Win 10/11 + Chrome / Edge с поддержкой WebView2
- Рекомендуется: Intel N95/N100 моноблок 1080p или встроенный SoC телевизора
- Сеть: интернет (для live URL) либо Wi-Fi для offline ZIP
- Разрешения: 1920×1080 (16:9 FHD), 3840×2160 (16:9 4K), 1080×1920 (9:16 вертикальный)
Установка (Live URL)
Касса/мини-ПК запускает Chromium в kiosk-mode при старте Windows:
- В планировщике задач (
taskschd.msc) создать задачу «При входе пользователя» → запустить:C:\Program Files\Google\Chrome\Application\chrome.exe ^ --kiosk ^ --noerrdialogs --disable-translate --no-first-run ^ --disable-features=TranslateUI --disable-pinch ^ https://erp-test.nirbi.ru/r/bar-mainscreen - Или ярлык в
shell:startupс теми же параметрами
Установка (Offline ZIP)
- Скачать
.zipиз админки → распаковать на C:\menu\ - Создать ярлык:
chrome.exe --kiosk file:///C:/menu/index.html
Общие технические детали
Загрузка страницы
GET /r/{slug} отдаёт HTML с:
- Embedded JSON меню в
<script id="menu-data" type="application/json">{...}</script>(предотвращает дополнительный round-trip) - Inlined CSS в
<style>(для FCP < 100ms) - JS bundle (~50KB gzipped) для WebSocket + рендера
Live-обновление
Приоритет 1: WebSocket /r/{slug}/stream
const ws = new WebSocket(`wss://${host}/r/${slug}/stream`);
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'menu_updated' && msg.version_hash !== currentHash) {
refetchAndRerender();
}
if (msg.type === 'menu_unpublished' || msg.type === 'menu_archived') {
showOfflineBanner();
}
};Приоритет 2: Polling fallback (если WebSocket упал и не восстанавливается):
- Раз в 30 сек
GET /r/{slug}/data - Сравнить
version_hashс текущим — если изменился, перерисовать - Бесплатно — uses HTTP cache
Reconnect логика
- При обрыве WebSocket — экспоненциальный backoff (1s, 2s, 4s, 8s, max 30s)
- Если connection lost > 60 сек — переключиться на polling
- При восстановлении WebSocket — отключить polling
Heartbeat
- Каждые 25 сек сервер шлёт
{ "type": "ping" } - Клиент отвечает
{ "type": "pong" } - Если клиент не получил ping за 60 сек — считаем connection lost
Heartbeat-индикатор для админа
- В правом нижнем углу маленький индикатор (видим в preview-mode):
- 🟢 «Live» — WebSocket активен
- 🟡 «Polling» — polling-режим
- 🔴 «Offline» — нет соединения
В обычном режиме (не preview) — индикатор скрыт, чтобы не мешать рекламе.
Шаблон grid — Сетка карточек
Классическая сетка карточек товаров. Подходит для кофеен, бургерных, ресторанов с ассортиментом 20-50 позиций.
┌─ Header ─────────────────────────────────────────────┐
│ [Логотип] / [Название меню] │
├──────────────────────────────────────────────────────┤
│ ── Кофе ──────────────────────────────────────── │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 🖼 │ │ 🖼 │ │ 🖼 │ │ 🖼 │ │
│ │ [фото] │ │ [фото] │ │ [фото] │ │ [фото] │ │
│ ├─────────┤ ├─────────┤ ├─────────┤ ├─────────┤ │
│ │Капучино │ │ Латте │ │ Эспрес. │ │ Раф │ │
│ │ 290 ₽ │ │ 310 ₽ │ │ 250 ₽ │ │ 330 ₽ │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ── Десерты ───────────────────────────────────── │
│ ┌─────────┐ ┌─────────┐ │
│ │ ... │ │ ... │ │
│ └─────────┘ └─────────┘ │
└──────────────────────────────────────────────────────┘
Параметры layout
- Колонок в сетке per resolution:
- 1080p (1920×1080): 4 колонки
- 4K (3840×2160): 6 колонок
- 9:16 (1080×1920): 2 колонки
- Карточка:
aspect-ratio: 3/4, фото вверху ~60% высоты, под ним название и цена - Заголовок категории: 36px, отступ сверху 24px
- Без скролла — если товары не помещаются, авто-перелистывание категорий каждые 15 сек (плавная анимация)
Адаптивность
CSS clamp + media queries:
.grid {
grid-template-columns: repeat(var(--cols), 1fr);
gap: clamp(16px, 1.5vw, 32px);
}
@media (orientation: portrait) { --cols: 2; }
@media (min-width: 3000px) { --cols: 6; }
@media (min-width: 1900px) and (orientation: landscape) { --cols: 4; }Анимации
- При обновлении меню (live) — плавное cross-fade нового состояния поверх старого (300 ms)
- Новые товары — fade-in
- Удалённые товары — fade-out
Шаблон slider — Крупный фокус
Один товар на весь экран, авто-перелистывание. Подходит для рекламного режима — большая фотка + крупные цена и описание.
┌──────────────────────────────────────────────────────┐
│ │
│ ┌──────────────────┐ Категория «Кофе» │
│ │ │ │
│ │ 🖼 │ КАПУЧИНО │
│ │ [фото 60%] │ │
│ │ │ Двойной эспрессо + │
│ │ │ мягкая молочная пенка │
│ └──────────────────┘ │
│ │
│ 290 ₽ │
│ │
│ ● ○ ○ ○ ○ │
└──────────────────────────────────────────────────────┘
Параметры
- Тайминг автоперелистывания: 5 сек на товар (настраиваемо в
external_menuв P1, в P0 hardcode) - Переход: slide-left 600 ms, easing ease-in-out
- Pagination dots внизу
- Категория — в левом верхнем углу мелким текстом
Адаптивность
- На 16:9 — фото слева, текст справа
- На 9:16 — фото сверху, текст снизу
Шаблон list — Длинный список
Для табло «Бизнес-ланч»: список с большой ценой справа. Подходит когда товаров мало (до 12) и нужно быстро прочесть.
┌──────────────────────────────────────────────────────┐
│ Бизнес-ланч до 16:00 │
├──────────────────────────────────────────────────────┤
│ ── Супы ────────────────────────────────────── │
│ Борщ 250 ₽ │
│ Грибной крем 280 ₽ │
│ │
│ ── Горячее ────────────────────────────────── │
│ Котлета по-киевски 450 ₽ │
│ Паста карбонара 520 ₽ │
│ │
│ ── Напитки ───────────────────────────────── │
│ Морс ягодный 150 ₽ │
└──────────────────────────────────────────────────────┘
Параметры
- Один экран — без скролла. Если товары не помещаются — авто-перелистывание страниц 8 сек
- Большой шрифт — minimum 32px на 1080p, 48px на 4K
- Цена — выровнена справа, dotted leader between
Адаптивность
- На 9:16 — pure list без двух колонок
- На 16:9 — может быть две колонки (если товаров > 8)
Поведение при ошибках
| Сценарий | Что показываем |
|---|---|
Меню archived (404 от /r/{slug}) | Полноэкранный banner: «Меню снято с публикации» + контактная инфа франшизы (опционально) |
Меню в draft (404) | Аналогично |
connection lost > 60s AND polling-fallback тоже падает | Полупрозрачный banner внизу: «Нет соединения. Показано последнее обновление в 14:32» — содержимое продолжает крутиться |
| HTTP 500 при загрузке страницы | Базовая HTML «Сервис временно недоступен. Попробуйте позже» + auto-reload через 60 сек |
| WebSocket close с code=4040 (server says menu archived) | Banner «Меню больше не доступно» |
Preview-режим
/r/{slug}?preview=1 (с preview-токеном):
- Работает даже для
draftменю - В правом нижнем углу — индикатор
🟡 PREVIEW(видимо) - Не подключается к WebSocket / polling — статичный snapshot
- Используется владельцем для проверки перед публикацией
Preview-токен:
- Устанавливается админкой (страницей конструктора) через cookie
_external_menu_previewсо временем жизни 1 час - Cookie HttpOnly + SameSite=Lax + secure
- Сервер при
?preview=1проверяет cookie — если нет / истёк → 401
Offline ZIP — содержимое
GET /external-menus/{id}/export.zip отдаёт архив:
ERP-POS-menu-bar-mainscreen-2026-04-28.zip
├── index.html ← entry point (двойной клик)
├── style.css ← inlined fonts + theming
├── menu.json ← snapshot данных меню
├── assets/
│ ├── images/
│ │ ├── product-1.webp
│ │ ├── product-2.webp
│ │ └── ...
│ └── fonts/
│ └── Inter-Bold.woff2
└── README.txt ← «Откройте index.html в Chrome / Edge для отображения меню»
Особенности offline режима
- Без auto-refresh — snapshot на момент скачивания
- Без stop-list обновлений — товары которые в стопе на момент скачивания скрыты, дальше заморожено
- Шаблон тот же что выбран в админке (grid / slider / list)
- Размер архива — обычно 2-15 MB (зависит от количества товаров и размера фотографий)
- При смене меню — нужно скачать новый ZIP
Метрики (для аналитики, P1)
В P0 — никаких. В P1 можно собирать:
- Heartbeat’ы от мониторов (косвенно показывает что монитор работает)
- Время рендера / FCP / LCP
- Ошибки JS
Ссылки
- BR 4.1
- Бизнес-спека
- Catalog Service · API — endpoints
/r/{slug}* - Внешние меню — Список
- Внешние меню — Конструктор