<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Духовка on Рецепты</title><link>https://recipes.dmlab.work/tags/%D0%B4%D1%83%D1%85%D0%BE%D0%B2%D0%BA%D0%B0/</link><description>Recent content in Духовка on Рецепты</description><generator>Hugo -- 0.155.3</generator><language>ru-ru</language><lastBuildDate>Wed, 05 Nov 2025 14:00:00 +0300</lastBuildDate><atom:link href="https://recipes.dmlab.work/tags/%D0%B4%D1%83%D1%85%D0%BE%D0%B2%D0%BA%D0%B0/index.xml" rel="self" type="application/rss+xml"/><item><title>Котлеты итальянские с базиликом и вялеными томатами</title><link>https://recipes.dmlab.work/recipes/kotlety-italyanskie/</link><pubDate>Wed, 05 Nov 2025 14:00:00 +0300</pubDate><guid>https://recipes.dmlab.work/recipes/kotlety-italyanskie/</guid><description>&lt;h2 id="необходимое-оборудование"&gt;Необходимое оборудование&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Мясорубка с решёткой 3-5 мм или кухонный комбайн&lt;/li&gt;
&lt;li&gt;Мясной термометр-щуп (диапазон 0-100°C)&lt;/li&gt;
&lt;li&gt;Сковорода с антипригарным покрытием диаметром 26-28 см&lt;/li&gt;
&lt;li&gt;Духовой шкаф с точным контролем температуры&lt;/li&gt;
&lt;li&gt;Противень или жаропрочная форма&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="подготовка"&gt;Подготовка&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Куриное филе грудки и бедра довести до температуры 8-10°C (достать из холодильника за 15 минут)&lt;/li&gt;
&lt;li&gt;Сливки довести до комнатной температуры 20-22°C (1 час при комнатной температуре)&lt;/li&gt;
&lt;li&gt;Яйцо довести до комнатной температуры 20°C (30 минут вне холодильника)&lt;/li&gt;
&lt;li&gt;Листья базилика промыть холодной водой, обсушить бумажным полотенцем&lt;/li&gt;
&lt;li&gt;Филе нарезать кусками 3×3 см для равномерного измельчения&lt;/li&gt;
&lt;li&gt;Все ингредиенты отмерить с точностью ±2г&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Разогреть духовку до 180°C&lt;/strong&gt; за 15-20 минут до начала жарки&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="ингредиенты"&gt;Ингредиенты&lt;/h2&gt;
&lt;div class="nutritional-info"&gt;
&lt;h4&gt;Пищевая ценность на 100г:&lt;/h4&gt;
&lt;div class="nutritional-grid"&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Калории:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;226 ккал&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Белки:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;20.8 г&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Жиры:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;13.2 г&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Углеводы:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;6.4 г&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="recipe-calculator"&gt;
&lt;div class="servings-control"&gt;
&lt;label&gt;Количество порций:&lt;/label&gt;
&lt;button class="btn-servings" onclick="changeServings(-1)"&gt;−&lt;/button&gt;
&lt;input type="number" id="servings" value="6" min="1" onchange="recalculate()"&gt;
&lt;button class="btn-servings" onclick="changeServings(1)"&gt;+&lt;/button&gt;
&lt;button class="btn-print" onclick="printIngredients()"&gt;🖨️ Печать состава&lt;/button&gt;
&lt;/div&gt;
&lt;ul class="ingredients-list"&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="400"&gt;400&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Филе куриное (грудка, охлажденное 4-6°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="400"&gt;400&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Филе куриное (бедро без кожи, охлажденное 4-6°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="60"&gt;60&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Сухари панко (без добавок)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="1"&gt;1&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;шт&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Яйцо куриное С0 (комнатной температуры 20°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="100"&gt;100&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;мл&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Сливки 33% жирности (комнатной температуры 20°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="50"&gt;50&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Базилик свежий (листья без стеблей)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="60"&gt;60&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Томаты вяленые (сухой вес, без масла)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="100"&gt;100&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Пармезан тёртый (выдержка не менее 12 месяцев)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="16"&gt;16&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Соль поваренная пищевая&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="1.5"&gt;1.5&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Перец черный молотый&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="60"&gt;60&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;мл&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Масло растительное рафинированное (для жарки)&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="output-info"&gt;
&lt;div class="output-item"&gt;
&lt;strong&gt;Общий вес сырых ингредиентов:&lt;/strong&gt;
&lt;span id="totalWeight"&gt;&lt;/span&gt; г
&lt;/div&gt;
&lt;div class="output-item"&gt;
&lt;strong&gt;Выход готового блюда:&lt;/strong&gt;
&lt;span id="yieldWeight" data-yield-ratio="0.72"&gt;&lt;/span&gt; г
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.recipe-calculator {
margin: 20px 0;
padding: 20px;
background: var(--code-bg);
border-radius: 8px;
}
.servings-control {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.servings-control label {
font-weight: 600;
}
#servings {
width: 60px;
text-align: center;
padding: 5px;
border: 1px solid var(--border);
border-radius: 4px;
}
.btn-servings {
width: 30px;
height: 30px;
border: 1px solid var(--border);
background: var(--theme);
color: var(--primary);
border-radius: 4px;
cursor: pointer;
font-size: 18px;
line-height: 1;
}
.btn-servings:hover {
background: var(--primary);
color: var(--theme);
}
.ingredients-list {
list-style: none;
padding: 0;
margin: 0;
}
.ingredient-item {
padding: 8px 0;
border-bottom: 1px solid var(--border);
display: flex;
gap: 8px;
}
.ingredient-item:last-child {
border-bottom: none;
}
.ingredient-amount {
font-weight: 600;
min-width: 50px;
}
.ingredient-unit {
color: var(--secondary);
min-width: 40px;
}
.output-info {
margin-top: 20px;
padding: 15px;
background: var(--theme);
border: 1px solid var(--border);
border-radius: 4px;
}
.output-item {
margin-bottom: 8px;
}
.output-item:last-child {
margin-bottom: 0;
}
.nutritional-info {
margin: 0 0 20px 0;
padding: 15px;
background: var(--code-bg);
border-radius: 8px;
}
.nutritional-info h4 {
margin: 0 0 15px 0;
font-size: 16px;
color: var(--primary);
}
.nutritional-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
@media (min-width: 600px) {
.nutritional-grid {
grid-template-columns: repeat(4, 1fr);
}
}
.nutritional-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
background: var(--code-bg);
border-radius: 6px;
text-align: center;
}
.nutritional-label {
font-size: 12px;
color: var(--secondary);
margin-bottom: 4px;
}
.nutritional-value {
font-size: 16px;
font-weight: 600;
color: var(--primary);
}
.btn-print {
padding: 8px 16px;
background: var(--primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-left: 10px;
transition: background 0.2s ease;
}
.btn-print:hover {
background: var(--secondary);
}
.btn-print:disabled {
background: #ccc;
cursor: not-allowed;
}
&lt;/style&gt;
&lt;script&gt;
class NimbotPrinter {
constructor() {
this.device = null;
this.server = null;
this.service = null;
this.characteristic = null;
this.isConnected = false;
}
async connect() {
try {
this.device = await navigator.bluetooth.requestDevice({
filters: [
{ namePrefix: 'NIIMBOT' },
{ namePrefix: 'D11' },
{ namePrefix: 'B21' },
{ namePrefix: 'B1' }
],
optionalServices: ['49535343-fe7d-4ae5-8fa9-9fafd205e455']
});
this.server = await this.device.gatt.connect();
this.service = await this.server.getPrimaryService('49535343-fe7d-4ae5-8fa9-9fafd205e455');
this.characteristic = await this.service.getCharacteristic('49535343-8841-43f4-a8d4-ecbe34729bb3');
this.isConnected = true;
return true;
} catch (error) {
throw error;
}
}
disconnect() {
if (this.device &amp;&amp; this.device.gatt.connected) {
this.device.gatt.disconnect();
}
this.isConnected = false;
}
async printText(text) {
if (!this.isConnected) throw new Error('Принтер не подключен');
const encoder = new TextEncoder();
const data = encoder.encode(text + '\n\n');
const chunkSize = 20;
for (let i = 0; i &lt; data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
await this.characteristic.writeValue(chunk);
await new Promise(resolve =&gt; setTimeout(resolve, 10));
}
}
static isSupported() {
return 'bluetooth' in navigator;
}
}
const originalServings = 6 ;
document.addEventListener('DOMContentLoaded', function() {
recalculate();
});
function recalculate() {
const newServings = document.getElementById('servings').value;
const ratio = newServings / originalServings;
let totalWeight = 0;
document.querySelectorAll('.ingredient-amount').forEach(el =&gt; {
const original = parseFloat(el.dataset.original);
if (!isNaN(original)) {
const newAmount = original * ratio;
totalWeight += newAmount;
if (newAmount &lt; 10) {
el.textContent = (Math.round(newAmount * 10) / 10).toString().replace('.', ',');
} else {
el.textContent = Math.round(newAmount);
}
}
});
document.querySelectorAll('.dynamic-amount').forEach(el =&gt; {
const original = parseFloat(el.dataset.original);
const showUnit = el.dataset.showUnit === 'true';
const unit = el.dataset.unit || '';
const addText = el.dataset.addText || '';
if (!isNaN(original)) {
const newAmount = original * ratio;
let displayText = '';
if (newAmount &lt; 10) {
displayText = (Math.round(newAmount * 10) / 10).toString().replace('.', ',');
} else {
displayText = Math.round(newAmount).toString();
}
if (showUnit &amp;&amp; unit) {
displayText += ' ' + unit;
}
if (addText) {
displayText += ' ' + addText;
}
el.textContent = displayText;
}
});
const totalWeightEl = document.getElementById('totalWeight');
if (totalWeightEl) {
totalWeightEl.textContent = Math.round(totalWeight);
}
const yieldWeightEl = document.getElementById('yieldWeight');
if (yieldWeightEl) {
const yieldRatio = parseFloat(yieldWeightEl.dataset.yieldRatio) || 1.0;
const newYield = totalWeight * yieldRatio;
yieldWeightEl.textContent = Math.round(newYield);
}
}
function changeServings(delta) {
const input = document.getElementById('servings');
input.value = Math.max(1, parseInt(input.value) + delta);
recalculate();
}
async function printIngredients() {
const printBtn = document.querySelector('.btn-print');
printBtn.disabled = true;
printBtn.textContent = '⏳ Подготовка...';
try {
if (!NimbotPrinter.isSupported()) {
throw new Error('Web Bluetooth API не поддерживается браузером');
}
const recipeTitle = document.querySelector('h1')?.textContent || 'Рецепт';
const servings = document.getElementById('servings').value;
const ingredients = collectIngredients();
const totalWeight = document.getElementById('totalWeight')?.textContent || '0';
const yieldWeight = document.getElementById('yieldWeight')?.textContent || '0';
const printText = formatIngredientsForPrint(recipeTitle, servings, ingredients, totalWeight, yieldWeight);
const printer = new NimbotPrinter();
try {
printBtn.textContent = '📡 Поиск принтера...';
await printer.connect();
printBtn.textContent = '🖨️ Печать...';
await printer.printText(printText, { fontSize: 12, alignment: 'left' });
printer.disconnect();
printBtn.textContent = '✅ Напечатано';
setTimeout(() =&gt; {
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}, 2000);
} catch (printerError) {
console.warn('Реальная печать не удалась:', printerError);
printBtn.textContent = '📄 Предпросмотр';
showPrintDialog(printText);
setTimeout(() =&gt; {
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}, 1000);
}
} catch (error) {
console.error('Ошибка печати:', error);
alert('Не удалось напечатать: ' + error.message);
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}
}
function collectIngredients() {
const ingredients = [];
document.querySelectorAll('.ingredient-item').forEach(item =&gt; {
const amount = item.querySelector('.ingredient-amount')?.textContent;
const unit = item.querySelector('.ingredient-unit')?.textContent;
const name = item.querySelector('.ingredient-name')?.textContent;
if (amount &amp;&amp; name) {
ingredients.push({ amount, unit: unit || '', name });
}
});
return ingredients;
}
function formatIngredientsForPrint(title, servings, ingredients, totalWeight, yieldWeight) {
let text = `${title}\n`;
text += `Порций: ${servings}\n`;
text += '─'.repeat(20) + '\n';
ingredients.forEach(ing =&gt; {
text += `${ing.amount} ${ing.unit} ${ing.name}\n`;
});
text += '─'.repeat(20) + '\n';
text += `Общий вес: ${totalWeight}г\n`;
text += `Готового: ${yieldWeight}г\n`;
return text;
}
async function simulatePrint(text) {
return new Promise(resolve =&gt; setTimeout(resolve, 1500));
}
function showPrintDialog(text) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.8); display: flex; align-items: center;
justify-content: center; z-index: 1000;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white; padding: 20px; border-radius: 8px;
max-width: 400px; font-family: monospace; line-height: 1.4;
`;
dialog.innerHTML = `
&lt;h3&gt;Предпросмотр печати&lt;/h3&gt;
&lt;pre style="white-space: pre-wrap; background: #f5f5f5; padding: 10px; border-radius: 4px;"&gt;${text}&lt;/pre&gt;
&lt;button onclick="this.parentElement.parentElement.remove()" style="
background: var(--primary); color: white; border: none;
padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 10px;
"&gt;Закрыть&lt;/button&gt;
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
}
&lt;/script&gt;
&lt;h2 id="технология-приготовления"&gt;Технология приготовления&lt;/h2&gt;
&lt;h3 id="этап-1-приготовление-фарша"&gt;Этап 1: Приготовление фарша&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Время:&lt;/strong&gt; 15 минут
&lt;strong&gt;Температура:&lt;/strong&gt; 8-10°C (рабочая температура фарша)&lt;/p&gt;</description></item><item><title>Куриные котлеты сочные</title><link>https://recipes.dmlab.work/recipes/kurinye-kotlety-sochnye/</link><pubDate>Sun, 02 Nov 2025 14:00:00 +0300</pubDate><guid>https://recipes.dmlab.work/recipes/kurinye-kotlety-sochnye/</guid><description>&lt;h2 id="необходимое-оборудование"&gt;Необходимое оборудование&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Мясорубка с решёткой 3-5 мм или кухонный комбайн&lt;/li&gt;
&lt;li&gt;Мясной термометр-щуп (диапазон 0-100°C)&lt;/li&gt;
&lt;li&gt;Сковорода с антипригарным покрытием диаметром 26-28 см&lt;/li&gt;
&lt;li&gt;Духовой шкаф с точным контролем температуры&lt;/li&gt;
&lt;li&gt;Противень или жаропрочная форма&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="подготовка"&gt;Подготовка&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Куриное филе грудки и бедра довести до температуры 8-10°C (достать из холодильника за 15 минут)&lt;/li&gt;
&lt;li&gt;Сливки довести до комнатной температуры 20-22°C (1 час при комнатной температуре)&lt;/li&gt;
&lt;li&gt;Яйцо довести до комнатной температуры 20°C (30 минут вне холодильника)&lt;/li&gt;
&lt;li&gt;Все ингредиенты отмерить с точностью ±2г&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Разогреть духовку до 180°C&lt;/strong&gt; за 15-20 минут до начала жарки&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="ингредиенты"&gt;Ингредиенты&lt;/h2&gt;
&lt;div class="nutritional-info"&gt;
&lt;h4&gt;Пищевая ценность на 100г:&lt;/h4&gt;
&lt;div class="nutritional-grid"&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Калории:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;198 ккал&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Белки:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;18.2 г&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Жиры:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;11.4 г&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Углеводы:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;6.8 г&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="recipe-calculator"&gt;
&lt;div class="servings-control"&gt;
&lt;label&gt;Количество порций:&lt;/label&gt;
&lt;button class="btn-servings" onclick="changeServings(-1)"&gt;−&lt;/button&gt;
&lt;input type="number" id="servings" value="6" min="1" onchange="recalculate()"&gt;
&lt;button class="btn-servings" onclick="changeServings(1)"&gt;+&lt;/button&gt;
&lt;button class="btn-print" onclick="printIngredients()"&gt;🖨️ Печать состава&lt;/button&gt;
&lt;/div&gt;
&lt;ul class="ingredients-list"&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="450"&gt;450&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Филе куриное (грудка, охлажденное 4-6°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="450"&gt;450&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Филе куриное (бедро без кожи, охлажденное 4-6°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="140"&gt;140&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;мл&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Сливки 33% жирности (комнатной температуры 20°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="60"&gt;60&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Панировочные сухари (без добавок)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="1"&gt;1&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;шт&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Яйцо куриное С0 (комнатной температуры 20°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="14"&gt;14&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Соль поваренная пищевая&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="1.5"&gt;1.5&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Перец черный молотый&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="60"&gt;60&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;мл&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Масло растительное рафинированное (для жарки)&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="output-info"&gt;
&lt;div class="output-item"&gt;
&lt;strong&gt;Общий вес сырых ингредиентов:&lt;/strong&gt;
&lt;span id="totalWeight"&gt;&lt;/span&gt; г
&lt;/div&gt;
&lt;div class="output-item"&gt;
&lt;strong&gt;Выход готового блюда:&lt;/strong&gt;
&lt;span id="yieldWeight" data-yield-ratio="0.72"&gt;&lt;/span&gt; г
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.recipe-calculator {
margin: 20px 0;
padding: 20px;
background: var(--code-bg);
border-radius: 8px;
}
.servings-control {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.servings-control label {
font-weight: 600;
}
#servings {
width: 60px;
text-align: center;
padding: 5px;
border: 1px solid var(--border);
border-radius: 4px;
}
.btn-servings {
width: 30px;
height: 30px;
border: 1px solid var(--border);
background: var(--theme);
color: var(--primary);
border-radius: 4px;
cursor: pointer;
font-size: 18px;
line-height: 1;
}
.btn-servings:hover {
background: var(--primary);
color: var(--theme);
}
.ingredients-list {
list-style: none;
padding: 0;
margin: 0;
}
.ingredient-item {
padding: 8px 0;
border-bottom: 1px solid var(--border);
display: flex;
gap: 8px;
}
.ingredient-item:last-child {
border-bottom: none;
}
.ingredient-amount {
font-weight: 600;
min-width: 50px;
}
.ingredient-unit {
color: var(--secondary);
min-width: 40px;
}
.output-info {
margin-top: 20px;
padding: 15px;
background: var(--theme);
border: 1px solid var(--border);
border-radius: 4px;
}
.output-item {
margin-bottom: 8px;
}
.output-item:last-child {
margin-bottom: 0;
}
.nutritional-info {
margin: 0 0 20px 0;
padding: 15px;
background: var(--code-bg);
border-radius: 8px;
}
.nutritional-info h4 {
margin: 0 0 15px 0;
font-size: 16px;
color: var(--primary);
}
.nutritional-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
@media (min-width: 600px) {
.nutritional-grid {
grid-template-columns: repeat(4, 1fr);
}
}
.nutritional-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
background: var(--code-bg);
border-radius: 6px;
text-align: center;
}
.nutritional-label {
font-size: 12px;
color: var(--secondary);
margin-bottom: 4px;
}
.nutritional-value {
font-size: 16px;
font-weight: 600;
color: var(--primary);
}
.btn-print {
padding: 8px 16px;
background: var(--primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-left: 10px;
transition: background 0.2s ease;
}
.btn-print:hover {
background: var(--secondary);
}
.btn-print:disabled {
background: #ccc;
cursor: not-allowed;
}
&lt;/style&gt;
&lt;script&gt;
class NimbotPrinter {
constructor() {
this.device = null;
this.server = null;
this.service = null;
this.characteristic = null;
this.isConnected = false;
}
async connect() {
try {
this.device = await navigator.bluetooth.requestDevice({
filters: [
{ namePrefix: 'NIIMBOT' },
{ namePrefix: 'D11' },
{ namePrefix: 'B21' },
{ namePrefix: 'B1' }
],
optionalServices: ['49535343-fe7d-4ae5-8fa9-9fafd205e455']
});
this.server = await this.device.gatt.connect();
this.service = await this.server.getPrimaryService('49535343-fe7d-4ae5-8fa9-9fafd205e455');
this.characteristic = await this.service.getCharacteristic('49535343-8841-43f4-a8d4-ecbe34729bb3');
this.isConnected = true;
return true;
} catch (error) {
throw error;
}
}
disconnect() {
if (this.device &amp;&amp; this.device.gatt.connected) {
this.device.gatt.disconnect();
}
this.isConnected = false;
}
async printText(text) {
if (!this.isConnected) throw new Error('Принтер не подключен');
const encoder = new TextEncoder();
const data = encoder.encode(text + '\n\n');
const chunkSize = 20;
for (let i = 0; i &lt; data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
await this.characteristic.writeValue(chunk);
await new Promise(resolve =&gt; setTimeout(resolve, 10));
}
}
static isSupported() {
return 'bluetooth' in navigator;
}
}
const originalServings = 6 ;
document.addEventListener('DOMContentLoaded', function() {
recalculate();
});
function recalculate() {
const newServings = document.getElementById('servings').value;
const ratio = newServings / originalServings;
let totalWeight = 0;
document.querySelectorAll('.ingredient-amount').forEach(el =&gt; {
const original = parseFloat(el.dataset.original);
if (!isNaN(original)) {
const newAmount = original * ratio;
totalWeight += newAmount;
if (newAmount &lt; 10) {
el.textContent = (Math.round(newAmount * 10) / 10).toString().replace('.', ',');
} else {
el.textContent = Math.round(newAmount);
}
}
});
document.querySelectorAll('.dynamic-amount').forEach(el =&gt; {
const original = parseFloat(el.dataset.original);
const showUnit = el.dataset.showUnit === 'true';
const unit = el.dataset.unit || '';
const addText = el.dataset.addText || '';
if (!isNaN(original)) {
const newAmount = original * ratio;
let displayText = '';
if (newAmount &lt; 10) {
displayText = (Math.round(newAmount * 10) / 10).toString().replace('.', ',');
} else {
displayText = Math.round(newAmount).toString();
}
if (showUnit &amp;&amp; unit) {
displayText += ' ' + unit;
}
if (addText) {
displayText += ' ' + addText;
}
el.textContent = displayText;
}
});
const totalWeightEl = document.getElementById('totalWeight');
if (totalWeightEl) {
totalWeightEl.textContent = Math.round(totalWeight);
}
const yieldWeightEl = document.getElementById('yieldWeight');
if (yieldWeightEl) {
const yieldRatio = parseFloat(yieldWeightEl.dataset.yieldRatio) || 1.0;
const newYield = totalWeight * yieldRatio;
yieldWeightEl.textContent = Math.round(newYield);
}
}
function changeServings(delta) {
const input = document.getElementById('servings');
input.value = Math.max(1, parseInt(input.value) + delta);
recalculate();
}
async function printIngredients() {
const printBtn = document.querySelector('.btn-print');
printBtn.disabled = true;
printBtn.textContent = '⏳ Подготовка...';
try {
if (!NimbotPrinter.isSupported()) {
throw new Error('Web Bluetooth API не поддерживается браузером');
}
const recipeTitle = document.querySelector('h1')?.textContent || 'Рецепт';
const servings = document.getElementById('servings').value;
const ingredients = collectIngredients();
const totalWeight = document.getElementById('totalWeight')?.textContent || '0';
const yieldWeight = document.getElementById('yieldWeight')?.textContent || '0';
const printText = formatIngredientsForPrint(recipeTitle, servings, ingredients, totalWeight, yieldWeight);
const printer = new NimbotPrinter();
try {
printBtn.textContent = '📡 Поиск принтера...';
await printer.connect();
printBtn.textContent = '🖨️ Печать...';
await printer.printText(printText, { fontSize: 12, alignment: 'left' });
printer.disconnect();
printBtn.textContent = '✅ Напечатано';
setTimeout(() =&gt; {
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}, 2000);
} catch (printerError) {
console.warn('Реальная печать не удалась:', printerError);
printBtn.textContent = '📄 Предпросмотр';
showPrintDialog(printText);
setTimeout(() =&gt; {
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}, 1000);
}
} catch (error) {
console.error('Ошибка печати:', error);
alert('Не удалось напечатать: ' + error.message);
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}
}
function collectIngredients() {
const ingredients = [];
document.querySelectorAll('.ingredient-item').forEach(item =&gt; {
const amount = item.querySelector('.ingredient-amount')?.textContent;
const unit = item.querySelector('.ingredient-unit')?.textContent;
const name = item.querySelector('.ingredient-name')?.textContent;
if (amount &amp;&amp; name) {
ingredients.push({ amount, unit: unit || '', name });
}
});
return ingredients;
}
function formatIngredientsForPrint(title, servings, ingredients, totalWeight, yieldWeight) {
let text = `${title}\n`;
text += `Порций: ${servings}\n`;
text += '─'.repeat(20) + '\n';
ingredients.forEach(ing =&gt; {
text += `${ing.amount} ${ing.unit} ${ing.name}\n`;
});
text += '─'.repeat(20) + '\n';
text += `Общий вес: ${totalWeight}г\n`;
text += `Готового: ${yieldWeight}г\n`;
return text;
}
async function simulatePrint(text) {
return new Promise(resolve =&gt; setTimeout(resolve, 1500));
}
function showPrintDialog(text) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.8); display: flex; align-items: center;
justify-content: center; z-index: 1000;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white; padding: 20px; border-radius: 8px;
max-width: 400px; font-family: monospace; line-height: 1.4;
`;
dialog.innerHTML = `
&lt;h3&gt;Предпросмотр печати&lt;/h3&gt;
&lt;pre style="white-space: pre-wrap; background: #f5f5f5; padding: 10px; border-radius: 4px;"&gt;${text}&lt;/pre&gt;
&lt;button onclick="this.parentElement.parentElement.remove()" style="
background: var(--primary); color: white; border: none;
padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 10px;
"&gt;Закрыть&lt;/button&gt;
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
}
&lt;/script&gt;
&lt;h2 id="технология-приготовления"&gt;Технология приготовления&lt;/h2&gt;
&lt;h3 id="этап-1-приготовление-фарша"&gt;Этап 1: Приготовление фарша&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Время:&lt;/strong&gt; 15 минут
&lt;strong&gt;Температура:&lt;/strong&gt; 8-10°C (рабочая температура фарша)&lt;/p&gt;</description></item><item><title>Говядина с овощами в красном вине</title><link>https://recipes.dmlab.work/recipes/govyadina-s-ovoshchami-v-krasnom-vine/</link><pubDate>Wed, 15 Oct 2025 00:00:00 +0000</pubDate><guid>https://recipes.dmlab.work/recipes/govyadina-s-ovoshchami-v-krasnom-vine/</guid><description>&lt;h2 id="необходимое-оборудование"&gt;Необходимое оборудование&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Чугунная или керамическая форма для запекания объемом 3-4 литра с крышкой&lt;/li&gt;
&lt;li&gt;Пищевая фольга (если нет крышки)&lt;/li&gt;
&lt;li&gt;Сковорода с толстым дном 26-28 см&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="подготовка"&gt;Подготовка&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Говядину достать из холодильника за 20 минут до приготовления, довести до температуры 8-10°C&lt;/li&gt;
&lt;li&gt;Красное вино довести до комнатной температуры 18-20°C&lt;/li&gt;
&lt;li&gt;Духовку разогреть до 200°C (режим верх/низ без конвекции)&lt;/li&gt;
&lt;li&gt;Все овощи вымыть, очистить, обсушить&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="ингредиенты"&gt;Ингредиенты&lt;/h2&gt;
&lt;div class="nutritional-info"&gt;
&lt;h4&gt;Пищевая ценность на 100г:&lt;/h4&gt;
&lt;div class="nutritional-grid"&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Калории:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;165 ккал&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Белки:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;16.2 г&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Жиры:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;7.8 г&lt;/span&gt;
&lt;/div&gt;
&lt;div class="nutritional-item"&gt;
&lt;span class="nutritional-label"&gt;Углеводы:&lt;/span&gt;
&lt;span class="nutritional-value"&gt;8.5 г&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="recipe-calculator"&gt;
&lt;div class="servings-control"&gt;
&lt;label&gt;Количество порций:&lt;/label&gt;
&lt;button class="btn-servings" onclick="changeServings(-1)"&gt;−&lt;/button&gt;
&lt;input type="number" id="servings" value="5" min="1" onchange="recalculate()"&gt;
&lt;button class="btn-servings" onclick="changeServings(1)"&gt;+&lt;/button&gt;
&lt;button class="btn-print" onclick="printIngredients()"&gt;🖨️ Печать состава&lt;/button&gt;
&lt;/div&gt;
&lt;ul class="ingredients-list"&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="1000"&gt;1000&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Говядина (лопатка или шея, охлажденная 4-6°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="300"&gt;300&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Лук репчатый (очищенный)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="250"&gt;250&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Морковь (очищенная)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="150"&gt;150&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Перец сладкий болгарский (очищенный от семян)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="400"&gt;400&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Помидоры свежие (без кожицы)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="15"&gt;15&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Чеснок (очищенный)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="500"&gt;500&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;мл&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Вино красное сухое (комнатной температуры 18-20°C)&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="8"&gt;8&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Соль поваренная пищевая&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="5"&gt;5&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Сахар белый кристаллический&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="2"&gt;2&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;г&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Перец черный молотый&lt;/span&gt;
&lt;/li&gt;
&lt;li class="ingredient-item"&gt;
&lt;span class="ingredient-amount" data-original="30"&gt;30&lt;/span&gt;
&lt;span class="ingredient-unit"&gt;мл&lt;/span&gt;
&lt;span class="ingredient-name"&gt;Масло растительное рафинированное&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="output-info"&gt;
&lt;div class="output-item"&gt;
&lt;strong&gt;Общий вес сырых ингредиентов:&lt;/strong&gt;
&lt;span id="totalWeight"&gt;&lt;/span&gt; г
&lt;/div&gt;
&lt;div class="output-item"&gt;
&lt;strong&gt;Выход готового блюда:&lt;/strong&gt;
&lt;span id="yieldWeight" data-yield-ratio="0.78"&gt;&lt;/span&gt; г
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.recipe-calculator {
margin: 20px 0;
padding: 20px;
background: var(--code-bg);
border-radius: 8px;
}
.servings-control {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.servings-control label {
font-weight: 600;
}
#servings {
width: 60px;
text-align: center;
padding: 5px;
border: 1px solid var(--border);
border-radius: 4px;
}
.btn-servings {
width: 30px;
height: 30px;
border: 1px solid var(--border);
background: var(--theme);
color: var(--primary);
border-radius: 4px;
cursor: pointer;
font-size: 18px;
line-height: 1;
}
.btn-servings:hover {
background: var(--primary);
color: var(--theme);
}
.ingredients-list {
list-style: none;
padding: 0;
margin: 0;
}
.ingredient-item {
padding: 8px 0;
border-bottom: 1px solid var(--border);
display: flex;
gap: 8px;
}
.ingredient-item:last-child {
border-bottom: none;
}
.ingredient-amount {
font-weight: 600;
min-width: 50px;
}
.ingredient-unit {
color: var(--secondary);
min-width: 40px;
}
.output-info {
margin-top: 20px;
padding: 15px;
background: var(--theme);
border: 1px solid var(--border);
border-radius: 4px;
}
.output-item {
margin-bottom: 8px;
}
.output-item:last-child {
margin-bottom: 0;
}
.nutritional-info {
margin: 0 0 20px 0;
padding: 15px;
background: var(--code-bg);
border-radius: 8px;
}
.nutritional-info h4 {
margin: 0 0 15px 0;
font-size: 16px;
color: var(--primary);
}
.nutritional-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
@media (min-width: 600px) {
.nutritional-grid {
grid-template-columns: repeat(4, 1fr);
}
}
.nutritional-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
background: var(--code-bg);
border-radius: 6px;
text-align: center;
}
.nutritional-label {
font-size: 12px;
color: var(--secondary);
margin-bottom: 4px;
}
.nutritional-value {
font-size: 16px;
font-weight: 600;
color: var(--primary);
}
.btn-print {
padding: 8px 16px;
background: var(--primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-left: 10px;
transition: background 0.2s ease;
}
.btn-print:hover {
background: var(--secondary);
}
.btn-print:disabled {
background: #ccc;
cursor: not-allowed;
}
&lt;/style&gt;
&lt;script&gt;
class NimbotPrinter {
constructor() {
this.device = null;
this.server = null;
this.service = null;
this.characteristic = null;
this.isConnected = false;
}
async connect() {
try {
this.device = await navigator.bluetooth.requestDevice({
filters: [
{ namePrefix: 'NIIMBOT' },
{ namePrefix: 'D11' },
{ namePrefix: 'B21' },
{ namePrefix: 'B1' }
],
optionalServices: ['49535343-fe7d-4ae5-8fa9-9fafd205e455']
});
this.server = await this.device.gatt.connect();
this.service = await this.server.getPrimaryService('49535343-fe7d-4ae5-8fa9-9fafd205e455');
this.characteristic = await this.service.getCharacteristic('49535343-8841-43f4-a8d4-ecbe34729bb3');
this.isConnected = true;
return true;
} catch (error) {
throw error;
}
}
disconnect() {
if (this.device &amp;&amp; this.device.gatt.connected) {
this.device.gatt.disconnect();
}
this.isConnected = false;
}
async printText(text) {
if (!this.isConnected) throw new Error('Принтер не подключен');
const encoder = new TextEncoder();
const data = encoder.encode(text + '\n\n');
const chunkSize = 20;
for (let i = 0; i &lt; data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
await this.characteristic.writeValue(chunk);
await new Promise(resolve =&gt; setTimeout(resolve, 10));
}
}
static isSupported() {
return 'bluetooth' in navigator;
}
}
const originalServings = 5 ;
document.addEventListener('DOMContentLoaded', function() {
recalculate();
});
function recalculate() {
const newServings = document.getElementById('servings').value;
const ratio = newServings / originalServings;
let totalWeight = 0;
document.querySelectorAll('.ingredient-amount').forEach(el =&gt; {
const original = parseFloat(el.dataset.original);
if (!isNaN(original)) {
const newAmount = original * ratio;
totalWeight += newAmount;
if (newAmount &lt; 10) {
el.textContent = (Math.round(newAmount * 10) / 10).toString().replace('.', ',');
} else {
el.textContent = Math.round(newAmount);
}
}
});
document.querySelectorAll('.dynamic-amount').forEach(el =&gt; {
const original = parseFloat(el.dataset.original);
const showUnit = el.dataset.showUnit === 'true';
const unit = el.dataset.unit || '';
const addText = el.dataset.addText || '';
if (!isNaN(original)) {
const newAmount = original * ratio;
let displayText = '';
if (newAmount &lt; 10) {
displayText = (Math.round(newAmount * 10) / 10).toString().replace('.', ',');
} else {
displayText = Math.round(newAmount).toString();
}
if (showUnit &amp;&amp; unit) {
displayText += ' ' + unit;
}
if (addText) {
displayText += ' ' + addText;
}
el.textContent = displayText;
}
});
const totalWeightEl = document.getElementById('totalWeight');
if (totalWeightEl) {
totalWeightEl.textContent = Math.round(totalWeight);
}
const yieldWeightEl = document.getElementById('yieldWeight');
if (yieldWeightEl) {
const yieldRatio = parseFloat(yieldWeightEl.dataset.yieldRatio) || 1.0;
const newYield = totalWeight * yieldRatio;
yieldWeightEl.textContent = Math.round(newYield);
}
}
function changeServings(delta) {
const input = document.getElementById('servings');
input.value = Math.max(1, parseInt(input.value) + delta);
recalculate();
}
async function printIngredients() {
const printBtn = document.querySelector('.btn-print');
printBtn.disabled = true;
printBtn.textContent = '⏳ Подготовка...';
try {
if (!NimbotPrinter.isSupported()) {
throw new Error('Web Bluetooth API не поддерживается браузером');
}
const recipeTitle = document.querySelector('h1')?.textContent || 'Рецепт';
const servings = document.getElementById('servings').value;
const ingredients = collectIngredients();
const totalWeight = document.getElementById('totalWeight')?.textContent || '0';
const yieldWeight = document.getElementById('yieldWeight')?.textContent || '0';
const printText = formatIngredientsForPrint(recipeTitle, servings, ingredients, totalWeight, yieldWeight);
const printer = new NimbotPrinter();
try {
printBtn.textContent = '📡 Поиск принтера...';
await printer.connect();
printBtn.textContent = '🖨️ Печать...';
await printer.printText(printText, { fontSize: 12, alignment: 'left' });
printer.disconnect();
printBtn.textContent = '✅ Напечатано';
setTimeout(() =&gt; {
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}, 2000);
} catch (printerError) {
console.warn('Реальная печать не удалась:', printerError);
printBtn.textContent = '📄 Предпросмотр';
showPrintDialog(printText);
setTimeout(() =&gt; {
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}, 1000);
}
} catch (error) {
console.error('Ошибка печати:', error);
alert('Не удалось напечатать: ' + error.message);
printBtn.textContent = '🖨️ Печать состава';
printBtn.disabled = false;
}
}
function collectIngredients() {
const ingredients = [];
document.querySelectorAll('.ingredient-item').forEach(item =&gt; {
const amount = item.querySelector('.ingredient-amount')?.textContent;
const unit = item.querySelector('.ingredient-unit')?.textContent;
const name = item.querySelector('.ingredient-name')?.textContent;
if (amount &amp;&amp; name) {
ingredients.push({ amount, unit: unit || '', name });
}
});
return ingredients;
}
function formatIngredientsForPrint(title, servings, ingredients, totalWeight, yieldWeight) {
let text = `${title}\n`;
text += `Порций: ${servings}\n`;
text += '─'.repeat(20) + '\n';
ingredients.forEach(ing =&gt; {
text += `${ing.amount} ${ing.unit} ${ing.name}\n`;
});
text += '─'.repeat(20) + '\n';
text += `Общий вес: ${totalWeight}г\n`;
text += `Готового: ${yieldWeight}г\n`;
return text;
}
async function simulatePrint(text) {
return new Promise(resolve =&gt; setTimeout(resolve, 1500));
}
function showPrintDialog(text) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.8); display: flex; align-items: center;
justify-content: center; z-index: 1000;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white; padding: 20px; border-radius: 8px;
max-width: 400px; font-family: monospace; line-height: 1.4;
`;
dialog.innerHTML = `
&lt;h3&gt;Предпросмотр печати&lt;/h3&gt;
&lt;pre style="white-space: pre-wrap; background: #f5f5f5; padding: 10px; border-radius: 4px;"&gt;${text}&lt;/pre&gt;
&lt;button onclick="this.parentElement.parentElement.remove()" style="
background: var(--primary); color: white; border: none;
padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 10px;
"&gt;Закрыть&lt;/button&gt;
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
}
&lt;/script&gt;
&lt;h2 id="технология-приготовления"&gt;Технология приготовления&lt;/h2&gt;
&lt;h3 id="этап-1-подготовка-мяса-и-обжарка"&gt;Этап 1: Подготовка мяса и обжарка&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Время:&lt;/strong&gt; 12 минут
&lt;strong&gt;Температура:&lt;/strong&gt; 180-200°C на поверхности сковороды&lt;/p&gt;</description></item></channel></rss>