/* ===== BrewOS Landing app ===== */ const { useState, useRef, useEffect, useMemo, useContext } = React; /* ---------- icons ---------- */ function LIcon({ n, s = 22 }) { const p = { width: s, height: s, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round' }; const M = { idle: <>, waste: <>, blind: <>, eye: <>, cost: <>, flow: <>, bell: <>, chart: <>, plan: <>, phone: <>, bolt: <>, plug: <>, check: <>, brew: <>, pour: <>, store: <>, tank: <> }; return {M[n] || null}; } /* ---------- number helpers ---------- */ const ru = (n) => Math.round(n).toLocaleString('ru-RU'); function useCountUp(target, dur = 600) { const [v, setV] = useState(target);const prev = useRef(target); useEffect(() => { const from = prev.current,to = target,t0 = performance.now(); let raf; const tick = (now) => {const k = Math.min(1, (now - t0) / dur);const e = 1 - Math.pow(1 - k, 3); setV(from + (to - from) * e);if (k < 1) raf = requestAnimationFrame(tick);else prev.current = to;}; raf = requestAnimationFrame(tick);return () => cancelAnimationFrame(raf); }, [target]); return v; } /* ---------- reveal on scroll ---------- */ function useReveal() { useEffect(() => { const els = [...document.querySelectorAll('.reveal')]; const io = new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) {e.target.classList.add('in');io.unobserve(e.target);} }), { threshold: .12 }); els.forEach((e) => io.observe(e));return () => io.disconnect(); }, []); } /* ---------- slider control ---------- */ function Slider({ label, value, set, min, max, step, fmt, unit, steps }) { if (steps) { const idx = Math.max(0, steps.indexOf(value)); const pct = idx / (steps.length - 1) * 100; return (
{label} {fmt ? fmt(value) : value}{unit && {unit}}
set(steps[parseInt(e.target.value)])} />
); } const pct = (value - min) / (max - min) * 100; return (
{label} {fmt ? fmt(value) : value}{unit && {unit}}
set(parseFloat(e.target.value))} />
); } /* ========== CALCULATOR ========== */ /* ---------- shared calculator model (context) ---------- */ const CalcCtx = React.createContext(null); window.CalcCtx = CalcCtx; function useCalcModel() { const [tanks, setTanks] = useState(8); const [vol, setVol] = useState(1000); const [cycle, setCycle] = useState(24); const [price, setPrice] = useState(250); // отпускная цена ₽/л const [cost, setCost] = useState(130); // себестоимость ₽/л const margin = Math.max(0, price - cost); // прибыль с литра = цена − себес const r = useMemo(() => { const baseUtil = 0.61,newUtil = 0.83; // загрузка танков до/после (крафт) const cyclesYr = 365 / cycle; const perTankYr = cyclesYr * vol; const extraLiters = tanks * perTankYr * (newUtil - baseUtil); const gainUtil = extraLiters * margin; const producedNew = tanks * perTankYr * newUtil; const brakLiters = producedNew * 0.025; const gainBrak = brakLiters * margin; const gainTime = 8 * 52 * 500; const total = gainUtil + gainBrak + gainTime; const price2 = 12900 * 12; // условная годовая подписка const payback = price2 / (total / 12); // в месяцах // proof-of-story derived figures (от тех же вводных) const varkiBase = Math.round(cyclesYr * baseUtil); const varkiNew = Math.round(cyclesYr * newUtil); return { extraLiters, gainUtil, gainBrak, gainTime, total, payback, price: price2, varkiBase, varkiNew }; }, [tanks, vol, cycle, margin]); return { tanks, setTanks, vol, setVol, cycle, setCycle, price, setPrice, cost, setCost, margin, r }; } function Calculator() { const { tanks, setTanks, vol, setVol, cycle, setCycle, price, setPrice, cost, setCost, margin, r } = useContext(CalcCtx);const totalAnim = useCountUp(r.total); return (

Сколько вы недозарабатываете прямо сейчас

Подвигайте ползунки под свой завод — расчёт пересчитается мгновенно.

Прибыль с литра (цена − себестоимость) {margin > 0 ? ru(margin) + ' ₽/л' : 'Цена ниже себеса'}
Дополнительная прибыль в год с BrewOS
+ {ru(totalAnim)}
≈ {ru(r.total / 12)} ₽ каждый месяц — деньги, которые сейчас теряются на простоях и ручном учёте.
Рост оборачиваемости танков (+{ru(r.extraLiters)} л/год) +{ru(r.gainUtil)} ₽
Меньше брака и списаний +{ru(r.gainBrak)} ₽
Экономия времени команды +{ru(r.gainTime)} ₽
Одна простаивавшая партия, возвращённая в работу ≈ {ru(vol * margin)} ₽
Это в разы больше месячной стоимости системы. Поэтому окупаемость считается неделями: достаточно вернуть в оборот всего одну партию за год.
{ e.preventDefault(); openBot(`calc_${tanks}-${vol}-${cycle}-${price}-${cost}-${Math.round(r.total)}`); }}>Получить расчёт под мой завод Стоимость системы ≈ {Math.max(1, Math.round(r.price / r.total * 100))}% от этой выгоды
Расчёт оценочный, под крафтовое производство. Прибыль с литра считается как отпускная цена минус себестоимость. В основе: рост загрузки ЦКТ с 61% до 83%, снижение брака на ~2,5% объёма, ~8 ч/нед сэкономленного времени команды. Реальные цифры зависят от ассортимента и процессов — посчитаем точно на демо.
); } /* ---------- mini tank for preview ---------- */ function PvTank({ state, fill, color, foam }) { return ; } /* ---------- usage flow: lifecycle stages + per-module mini screens ---------- */ const FLOW = [ { step: '01', stage: 'Варка', mods: 'Варочный порядок · Рецепты · Задачи', screen: 'brew', sys: 'Сменам ставятся задачи по плану, варка идёт по рецепту' }, { step: '02', stage: 'Брожение', mods: 'ЦКТ · Замеры · Алерты', screen: 'ferment', sys: 'Замеры плотности и температуры по графику, риски подсвечены' }, { step: '03', stage: 'Дозревание / охлаждение', mods: 'ЦКТ · Маршрут партии', screen: 'cold', sys: 'Статусы и сроки видны, партия идёт по техмаршруту' }, { step: '04', stage: 'Розлив', mods: 'План розлива · Склад · Задачи', screen: 'pour', sys: 'Готовая партия встаёт в план розлива, продукт приходует склад' }]; function TankMini({ num, cap, nm, kv, state, fill, color, foam, badge }) { return (
№ {num}{cap} л
{nm}
{kv}
{badge.t}
); } function renderFlowScreen(s) { if (s.screen === 'brew') return (
Варочный порядок
Гёзе абрикоссегодня
APA Сосназавтра
IPA Цитрачт
); if (s.screen === 'ferment') return (
Замер плотности · завтра 10:00
); if (s.screen === 'cold') return (
до розлива 3 дн
); // pour return (
План розлива
готовСтаут · 4500 лсегодня
готовГруша · 500 лпт
Склад+180 кег
); } /* ========== DATA for static sections ========== */ const LEAKS = [ { ic: 'idle', h: 'Танки простаивают', p: 'Пустой или передержанный ЦКТ — это деньги, которые не работают. Без живой картины цеха планируют «на глаз».', cost: 'до 20% оборачиваемости мимо кассы' }, { ic: 'waste', h: 'Брак и списания', p: 'Пропущенный замер плотности или температуры — и партия уходит в слив. Узнаёте об этом постфактум.', cost: '3–5% объёма в отходы' }, { ic: 'blind', h: 'Нет прозрачной себестоимости', p: 'Сколько реально стоит партия и с какой наценкой её продавать? В таблицах Excel это считают неделями.', cost: 'продажи ниже маржи' }]; const FEATURES = [ { ic: 'eye', h: 'Цех как на ладони', p: 'Каждый ЦКТ — живая иконка с уровнем, сортом и состоянием: брожение, дозревание, cold crash, готов. Один экран — весь завод.' }, { ic: 'bell', h: 'Замеры и алерты', p: 'Система ведёт график замеров плотности и температуры, напоминает о шагах и подсвечивает партии в зоне риска.' }, { ic: 'cost', h: 'Себестоимость партии', p: 'Стоимость каждой варки считается автоматически — видно маржу и рекомендованную цену ещё до розлива.' }, { ic: 'plan', h: 'Планирование розлива', p: 'Готовые партии сами встают в план розлива и варочный порядок. Никаких конфликтов за танк.' }, { ic: 'chart', h: 'Оборачиваемость и аналитика', p: 'Загрузка танков, выход продукта, длительность циклов — метрики, которые напрямую двигают прибыль.' }, { ic: 'flow', h: 'Маршрут партии', p: 'Весь путь от залива сусла до розлива — на одном таймлайне. Видно, что происходит и что дальше.' }]; const METRICS = [ { n: '+33%', l: 'партий в год с тех же танков', am: false, why: 'Загрузка ЦКТ растёт с 61% до 83% — тот же парк танков вмещает на треть больше варок (9→12 с танка).' }, { n: '−63%', l: 'брака и списаний', am: true, why: 'Замеры по графику и алерты по партиям в риске: доля списаний падает с 4,0% до 1,5% объёма.' }, { n: '+22 п.п.', l: 'загрузка ЦКТ (61→83%)', am: false, why: 'Освобождение танка видно в день готовности — следующая варка встаёт без простоя между партиями.' }, { n: '<1 мес', l: 'окупаемость системы', am: true, why: 'Стоимость системы — лишь несколько процентов от возвращаемой прибыли. Даже одна спасённая партия (≈ 120 000 ₽ маржи) перекрывает месяцы подписки.' }]; const METHOD = [ { k: 'Загрузка ЦКТ', t: 'Крафтовые цеха часто работают на 55–70%: танк ждёт розлива, мойки, лабораторных замеров. Видимость освобождения танков поднимает загрузку на 10–22 п.п. В калькуляторе — средний сценарий, не максимум.' }, { k: 'Брак', t: 'Бо́льшая часть списаний на крафте — из-за пропущенных или поздних замеров, а не рецептуры. График и алерты убирают именно эту причину — поэтому снижение реальное, а не «магическое».' }, { k: 'Маржа', t: 'Система не повышает цену за вас. Она показывает реальную себестоимость каждой партии — а где не продавать ниже маржи, решаете вы.' }, { k: 'Окупаемость', t: 'Стоимость системы — несколько процентов от возвращаемой прибыли. Одна возвращённая в работу партия (≈ 120 000 ₽ маржи) перекрывает месяцы подписки. Точную цену под ваш цех считаем на демо.' }]; const STEPS = [ { h: 'Заводим ваш цех', p: 'Переносим все ЦКТ, сорта и текущие партии в систему — обычно за один день вместе с вашим технологом.' }, { h: 'Команда работает в BrewOS', p: 'Операторы ведут замеры, директор видит весь завод в реальном времени. Excel и блокноты больше не нужны.' }, { h: 'Считаем прибыль', p: 'Через месяц сверяем: выросла загрузка танков, упал брак, видна себестоимость. Прибыль — в отчёте.' }]; /* ---------- preview card ---------- */ function PvCard({ num, cap, nm, kv, state, fill, color, foam, badge, bcls }) { return (
№ {num}{cap} л
{nm}
{kv}
{badge.led && }{badge.t}
); } /* ========== DEMO FORM (152-ФЗ согласие) ========== */ // ВНИМАНИЕ (для дизайнера): endpoint заявок и реквизиты оператора берутся из // внешнего файла config.js (window.BREWOS_CONFIG). НЕ хардкодить сюда и НЕ // удалять чтение из BREWOS_CONFIG ниже — иначе форма перестанет слать на бэкенд. const __CFG = (typeof window !== 'undefined' && window.BREWOS_CONFIG) || {}; // URL входа в веб-приложение (кнопка «Войти»). Берётся из config.js. const LOGIN_URL = __CFG.appLoginUrl || 'http://localhost:15173/login'; // Открыть бот заявок (для CTA-кнопок вне App: «Получить расчёт» и т.п.). // start — payload deep-link: 'demo' (просто демо) или 'calc_<ЦКТ>-<объём>-<цикл>-<цена>-<себес>-<выгода>'. const DEMO_BOT_URL = __CFG.demoBotUrl || 'https://t.me/Scan2Action_requiestbot?start=demo'; const BOT_BASE = DEMO_BOT_URL.split('?')[0]; function openBot(start) { window.open(BOT_BASE + '?start=' + (start || 'demo'), '_blank', 'noopener'); } function goDemo(e) { if (e) e.preventDefault(); openBot('demo'); } // Реквизиты оператора ПДн (152-ФЗ). Реальные значения — в config.js. const OPERATOR = __CFG.operator || { name: 'ООО «БрюОС»', inn: '0000000000', address: 'г. Москва, ул. Примерная, д. 1', email: 'privacy@brewos.ru', retention: '6 месяцев' }; // Куда отправлять заявки — из config.js (window.BREWOS_CONFIG). // endpoint — URL бэкенда (рекомендуется), telegram — прямая отправка (только тест). const LEADS = { endpoint: __CFG.leadEndpoint || '', telegram: __CFG.telegram || { botToken: '', chatId: '' } }; async function sendLead({ name, phone }){ const text = `🍺 Заявка на демо BrewOS\n👤 ${name}\n📞 ${phone}\n🕒 ${new Date().toLocaleString('ru-RU')}`; if (LEADS.endpoint){ const res = await fetch(LEADS.endpoint, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ name, phone, ts: Date.now() }) }); if(!res.ok) throw new Error('endpoint '+res.status); return; } if (LEADS.telegram.botToken && LEADS.telegram.chatId){ const res = await fetch(`https://api.telegram.org/bot${LEADS.telegram.botToken}/sendMessage`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ chat_id: LEADS.telegram.chatId, text }) }); if(!res.ok) throw new Error('telegram '+res.status); return; } // ничего не настроено — демо-режим (без реальной отправки) await new Promise(r=>setTimeout(r,500)); } function fmtPhone(v) { let d = v.replace(/\D/g, ''); if (d.startsWith('8')) d = '7' + d.slice(1); if (!d.startsWith('7')) d = '7' + d; d = d.slice(0, 11); const p = d.slice(1); let out = '+7'; if (p.length > 0) out += ' (' + p.slice(0, 3); if (p.length >= 3) out += ') ' + p.slice(3, 6); if (p.length >= 6) out += '-' + p.slice(6, 8); if (p.length >= 8) out += '-' + p.slice(8, 10); return out; } function DemoForm({ open, onClose }) { const [view, setView] = useState('form'); // form | policy | done const [name, setName] = useState(''); const [phone, setPhone] = useState(''); const [agree, setAgree] = useState(false); const [touched, setTouched] = useState(false); const [sending, setSending] = useState(false); const [err, setErr] = useState(false); useEffect(() => { if (open) {setView('form');setTouched(false);} }, [open]); useEffect(() => { const onKey = (e) => {if (e.key === 'Escape') onClose();}; if (open) window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [open, onClose]); const phoneDigits = phone.replace(/\D/g, '').length; const nameOk = name.trim().length >= 2; const phoneOk = phoneDigits >= 11; const canSubmit = nameOk && phoneOk && agree; const submit = async (e) => { e.preventDefault(); setTouched(true); if (!canSubmit || sending) return; setErr(false); setSending(true); try { await sendLead({ name: name.trim(), phone }); setView('done'); } catch (_e) { setErr(true); } finally { setSending(false); } }; if (!open) return null; return (
{if (e.target === e.currentTarget) onClose();}}>
{view === 'form' &&
Демонстрация BrewOS

Записаться на демо

Покажем систему на примере вашего цеха и посчитаем выгоду. Перезвоним в рабочее время — без спама и обязательств.

{err &&

Не удалось отправить заявку. Попробуйте ещё раз или позвоните нам.

}

Нажимая «Записаться», вы подтверждаете согласие. Данные не передаются третьим лицам для рекламы и не распространяются. Отозвать согласие — письмом на {OPERATOR.email}.

} {view === 'policy' &&

Согласие на обработку персональных данных

Настоящее согласие даётся в соответствии с Федеральным законом от 27.07.2006 № 152-ФЗ «О персональных данных» свободно, своей волей и в своём интересе, является конкретным, предметным, информированным, сознательным и однозначным.

Оператор
{OPERATOR.name}, ИНН {OPERATOR.inn}, {OPERATOR.address}. Контакт по вопросам ПДн: {OPERATOR.email}.
Цель обработки
Обработка заявки на демонстрацию продукта BrewOS и связь с вами для её исполнения.
Перечень персональных данных
Имя и номер телефона, указанные вами в форме.
Перечень действий
Сбор, запись, систематизация, хранение, уточнение, использование, удаление и уничтожение — смешанным способом (с использованием средств автоматизации и без).
Передача третьим лицам
Данные не распространяются и не передаются третьим лицам, кроме случаев, прямо предусмотренных законом. Распространение (публикация) персональных данных не осуществляется.
Срок и хранение
Данные обрабатываются до достижения цели, но не дольше {OPERATOR.retention} с момента обращения, либо до отзыва согласия. Хранение и обработка — на территории Российской Федерации.
Отзыв согласия
Согласие можно отозвать в любой момент, направив заявление на {OPERATOR.email}. Обращение рассматривается в срок не более 30 дней.

Подробнее — в Политике обработки персональных данных оператора.

} {view === 'done' &&

Заявка принята

Спасибо, {name.trim().split(' ')[0] || 'будем знакомы'}! Свяжемся с вами по указанному номеру в ближайшее рабочее время.

}
); } /* ========== APP ========== */ function App() { useReveal(); const calcModel = useCalcModel(); // «Записаться на демо» → сразу в Telegram-бот заявок (deep-link), без формы. const openDemo = (e) => { if (e) e.preventDefault(); const url = (window.BREWOS_CONFIG && window.BREWOS_CONFIG.demoBotUrl) || 'https://t.me/Scan2Action_requiestbot?start=demo'; window.open(url, '_blank', 'noopener'); }; return (
{/* NAV */}
BBrewOS
для заводов напитков {/* Кнопка «Войти» временно скрыта (вернуть — раскомментировать строку ниже) */} {/* Войти */} Записаться на демо
{/* HERO */}
СИСТЕМА УПРАВЛЕНИЯ КРАФТОВЫМ ПРОИЗВОДСТВОМ

Ваша пивоварня зарабатывает БОЛЬШЕ,
когда каждый ЦКТ под контролем

BrewOS превращает цех брожения в управляемую систему: меньше простоев ЦКТ, меньше брака, прозрачная себестоимость партии. Прибыль, которая раньше утекала — остаётся у вас.

Внедрение за 1 день · работает на любом ассортименте
Цилиндро-конический танк с пивом
Состояние
FERMENTING
До розлива
6 дней
Загрузка цеха
84%
{/* METRICS */}
Результат

Что меняется на пивоварне за первый квартал

{METRICS.map((m, i) =>
{m.n}
{m.l}
{m.why}
)}
Диапазон пилота на крафтовом цехе 8 ЦКТ. Свои цифры — в калькуляторе ниже; модель открыта дальше.
{/* PROBLEM */}
Где утекают деньги

На крафтовом производстве прибыль теряется тихо — по литру за раз

Пока цех живёт в голове технолога и таблицах Excel, эти потери невидимы. Их не замечают — но в конце года они складываются в миллионы.

{LEAKS.map((l, i) =>

{l.h}

{l.p}

↘ {l.cost}
)}
{/* CALCULATOR */}
Калькулятор выгоды

Посчитайте, сколько BrewOS принесёт именно вашему заводу

{/* FEATURES */}
Что внутри

Весь цех брожения — в одной системе

Не очередная «таблица в облаке», а инструмент управления, заточенный под ЦКТ и партии напитков.

{FEATURES.map((f, i) =>

{f.h}

{f.p}

)}
{/* usage flow scheme */}
BrewOS · как партия проходит через систему
{FLOW.map((s, i, arr) =>
{s.step}{s.stage}
{s.mods}
{renderFlowScreen(s)}
{s.sys}
{i < arr.length - 1 &&
}
)}
Одна система ведёт партию через все цеха — варка, ЦКТ, розлив и склад связаны, без блокнотов и Excel.
{/* module mockups: план розлива + задачи */}
Не только танки Планирование розлива и задачи смен — в той же системе
BrewOS · План розлива
ДатаПартияОбъёмСтатус
{[ { when: 'сегодня', nm: 'Стаут имперский', tara: 'кега 30 л', vol: '4500 л', st: 'готов', cls: 'ready' }, { when: 'сегодня', nm: 'Шорле груша', tara: 'банка 0,5', vol: '500 л', st: 'в работе', cls: 'work' }, { when: 'завтра', nm: 'Пшеничное', tara: 'кега 30 л', vol: '2000 л', st: 'ждёт', cls: 'wait' }, { when: 'пт', nm: 'Montmorency', tara: 'банка 0,33', vol: '1000 л', st: 'план', cls: 'plan' }]. map((r, i) =>
{r.when} {r.nm}{r.tara} {r.vol} {r.st}
)}
На розлив сегодня 5000 л · свободных кег 120
BrewOS · Задачи
{[ { t: 'Замер плотности · ЦКТ №1', who: 'ТЛ', clr: '#34d399', meta: 'Технолог · сегодня 10:00', pri: 'срочно', done: false }, { t: 'Мойка CIP · ЦКТ №3', who: 'СА', clr: '#38bdf8', meta: 'Смена А · сегодня', pri: null, done: false }, { t: 'Подготовить розлив · Стаут', who: 'СБ', clr: '#a78bfa', meta: 'Смена Б · сегодня', pri: null, done: false }, { t: 'Принять солод Pilsner', who: 'СН', clr: '#f59e0b', meta: 'Снабжение · вчера', pri: null, done: true }]. map((r, i) =>
{r.t}
{r.meta}
{r.pri && {r.pri}} {r.who}
)}
3 активных · 1 выполнена сегодня
{/* METHODOLOGY */}
Честно о расчётах

Откуда берутся эти цифры

Никакой магии — четыре допущения, каждое можно проверить на своём цехе. Поэтому в калькуляторе мы даём диапазон, а не одну красивую цифру.

{METHOD.map((m, i) =>
{m.k}
{m.t}
)}
Мы сознательно берём консервативные допущения. На демо подставим ваши реальные данные и сверим цифры вместе.
Как внедряем

От первого экрана до прибыли — три шага

{STEPS.map((s, i) =>
0{i + 1}

{s.h}

{s.p}

)}
{/* CTA */}

Получи свою выгоду с BrewOS

Покажем BrewOS на примере вашего цеха и вместе посчитаем, сколько прибыли он вернёт. Без обязательств.

Внедрение за 1 день · первый месяц — с поддержкой технолога BrewOS
{/* FOOTER */}
BBrewOS
Система управления производством напитков © 2026 BrewOS
); } ReactDOM.createRoot(document.getElementById('root')).render();