Files
cookbook/app/templates/recipe_detail.html
T

400 lines
15 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>{{ recipe.title }} – Mein Kochbuch</title>
<link rel="stylesheet" href="/static/cookbook.css?v=4.6.9">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🍽️</text></svg>">
</head>
<body class="cookbook-body">
<!-- Navigation Bar -->
<header class="cb-nav">
<a href="/cookbook" class="cb-nav-brand">
<span class="cb-brand-icon">📖</span>
<h1 class="cb-nav-title">Kochbuch</h1>
</a>
<!-- Desktop Navigation -->
<div class="cb-nav-actions desktop-nav-actions">
<div class="cb-nav-cookmode-toggle no-print" title="Kochmodus: Verhindert automatisches Sperren des Bildschirms">
<span>👨‍🍳 Kochmodus</span>
<label class="cb-switch cb-switch-sm" for="cook-mode-toggle-nav">
<input type="checkbox" id="cook-mode-toggle-nav" class="cook-mode-switch-input" onchange="toggleCookingMode(this.checked)">
<span class="cb-slider"></span>
</label>
</div>
<a href="/cookbook" class="cb-btn cb-btn-outline">
<span>📖</span> <span>Alle Rezepte</span>
</a>
<a href="/cookbook/edit/{{ recipe.id }}" class="cb-btn cb-btn-green">
<span>✏️</span> <span>Bearbeiten</span>
</a>
<button onclick="window.print()" class="cb-btn cb-btn-outline" title="Rezeptseite drucken">
<span>🖨️</span> <span>Drucken</span>
</button>
<a href="/logout" class="cb-nav-btn cb-nav-btn-logout" title="Abmelden">
<span>🚪</span> <span>Abmelden</span>
</a>
</div>
<!-- Mobile Kebab (3-Punkte) Menü -->
<div class="mobile-nav-wrapper">
<button type="button" class="cb-kebab-btn" onclick="toggleMobileMenu(event)" aria-label="Menü öffnen" title="Menü">
<span>⋮</span>
</button>
<div id="cb-mobile-menu" class="cb-mobile-dropdown">
<div class="cb-mobile-item" style="display: flex; justify-content: space-between; align-items: center;" onclick="event.stopPropagation();">
<span>👨‍🍳 Kochmodus (Display an)</span>
<label class="cb-switch cb-switch-sm" for="cook-mode-toggle-mobile">
<input type="checkbox" id="cook-mode-toggle-mobile" class="cook-mode-switch-input" onchange="toggleCookingMode(this.checked)">
<span class="cb-slider"></span>
</label>
</div>
<hr class="cb-mobile-divider">
<a href="/cookbook" class="cb-mobile-item">
<span>📖</span> <span>Zum Kochbuch</span>
</a>
<a href="/cookbook/edit/{{ recipe.id }}" class="cb-mobile-item">
<span>✏️</span> <span>Rezept bearbeiten</span>
</a>
<button type="button" onclick="window.print()" class="cb-mobile-item">
<span>🖨️</span> <span>Drucken & PDF</span>
</button>
<hr class="cb-mobile-divider">
<a href="/logout" class="cb-mobile-item cb-mobile-logout">
<span>🚪</span> <span>Abmelden</span>
</a>
</div>
</div>
</header>
<main class="book-wrapper">
<!-- EINHEITLICHE REZEPTSEITE -->
<article class="page recipe-page">
<!-- KOCHMODUS SCHIEBEREGLER BANNER (Mobil, Tablet & Desktop) -->
<div class="recipe-cookmode-banner no-print">
<div class="recipe-cookmode-left">
<span class="cookmode-icon">👨‍🍳</span>
<div class="cookmode-text-group">
<span class="cookmode-main-text">Kochmodus</span>
<span class="cookmode-sub-text" id="cookmode-sub-text">Verhindert, dass das Display während des Kochens sperrt</span>
</div>
</div>
<div class="recipe-cookmode-right">
<label class="cb-switch" for="cook-mode-toggle" title="Kochmodus umschalten">
<input type="checkbox" id="cook-mode-toggle" class="cook-mode-switch-input" onchange="toggleCookingMode(this.checked)">
<span class="cb-slider"></span>
</label>
<span id="cook-mode-badge" class="cookmode-badge">Aus</span>
</div>
</div>
<!-- Header (Ohne Kapitelangabe & ohne Quelle im Titel) -->
<header class="recipe-header">
<h2 class="recipe-title">{{ recipe.title }}</h2>
{% if recipe.subtitle and recipe.subtitle.strip() %}
<div class="recipe-subtitle">{{ recipe.subtitle.strip() }}</div>
{% endif %}
<div class="recipe-meta-row">
{% if recipe.effort %}
<div class="recipe-meta-item">
<span>⏱️</span>
<span>Aufwand:
{% if recipe.effort == 'gering' %}Gering 🟢{% elif recipe.effort == 'hoch' %}Hoch 🔴{% else %}Mittel 🟡{% endif %}
</span>
</div>
{% endif %}
{% if recipe.prep_time %}
<div class="recipe-meta-item">
<span>🔪</span>
<span>Zubereitung: {{ recipe.prep_time }}</span>
</div>
{% endif %}
{% if recipe.cook_time %}
<div class="recipe-meta-item">
<span>🍳</span>
<span>Kochen/Backen: {{ recipe.cook_time }}</span>
</div>
{% endif %}
{% if recipe.portions %}
<div class="recipe-meta-item">
<span>👥</span>
<span>{{ recipe.portions }} Portionen</span>
</div>
{% endif %}
{% if recipe.source and recipe.source.strip() %}
<div class="recipe-meta-item">
<span>📖</span>
<span>Quelle: {{ recipe.source.strip() }}</span>
</div>
{% endif %}
</div>
</header>
<!-- 2-Spalten Layout (Auf Tablets im Querformat als 2-Spalten Kochstation) -->
<div class="recipe-grid">
<!-- Linke Spalte: Foto + Zutaten -->
<aside class="recipe-side">
<div class="recipe-photo-box">
{% if recipe.image_url %}
<img src="{{ recipe.image_url }}" alt="{{ recipe.title }}">
{% else %}
<div class="recipe-photo-empty">
<span>📸</span>
<span>Kein Foto hinterlegt</span>
</div>
{% endif %}
</div>
<!-- Zutatenleiste ohne Portionsangabe (da schon in Kopfzeile) -->
<div class="ingredients-box {% if recipe.has_portion_table %}has-portion-matrix{% endif %}">
<h4 class="section-hand-title">Zutaten</h4>
{% if recipe.has_portion_table and recipe.portion_matrix %}
<!-- Tabelle für 2/4/6/8 Portionen (z. B. Waffeln / Pancakes) -->
<table class="portion-matrix-table">
<thead>
<tr>
<th class="matrix-th-ing">Zutat</th>
<th class="matrix-th-p">2 P.</th>
<th class="matrix-th-p">4 P.</th>
<th class="matrix-th-p">6 P.</th>
<th class="matrix-th-p">8 P.</th>
</tr>
</thead>
<tbody>
{% for row in recipe.portion_matrix %}
{% if row.is_header %}
<tr class="matrix-header-row">
<th colspan="5">{{ row.text }}</th>
</tr>
{% else %}
<tr class="matrix-row">
<td class="matrix-td-name">{{ row.name }}</td>
<td class="matrix-td-amt">{{ row.amounts[2] }}</td>
<td class="matrix-td-amt highlight">{{ row.amounts[4] }}</td>
<td class="matrix-td-amt">{{ row.amounts[6] }}</td>
<td class="matrix-td-amt">{{ row.amounts[8] }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% else %}
<!-- Normale kompakte Zutatenliste (Tippen zum Abhaken beim Kochen) -->
<ul class="ingredients-list">
{% for line in recipe.ingredients.split('\n') %}
{% if line.strip() %}
{% set raw_line = line.strip() %}
{% if raw_line.endswith(':') or raw_line.lower().startswith('für ') %}
<li class="ing-subheading">{{ raw_line }}</li>
{% else %}
<li class="ingredient-item" onclick="toggleIngredientCheck(this)" title="Tippen zum Abhaken">{{ raw_line }}</li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
{% endif %}
</div>
</aside>
<!-- Rechte Spalte: Zubereitung + Notizen + Quelle -->
<section class="recipe-main">
<div>
<h4 class="section-hand-title">Zubereitung</h4>
<div class="instructions-steps">
{% for step in recipe.instructions.split('\n') %}
{% if step.strip() %}
<div class="step-card" onclick="toggleStepCheck(this)" title="Tippen zum Abhaken dieses Schritts">
<span class="step-badge">{{ loop.index }}</span>
<div class="step-text">{{ step.strip() | strip_step_number }}</div>
<span class="step-done-check">✓</span>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% if recipe.notes and recipe.notes.strip() %}
<div class="notes-box">
<div class="notes-box-title">💡 Tipp & Notiz</div>
<div>{{ recipe.notes.strip() }}</div>
</div>
{% else %}
<div class="notes-box notes-box-empty print-only">
<div class="notes-box-title">💡 Eigene Notizen & Tipps</div>
<div class="notes-blank-lines">
<div class="notes-blank-line"></div>
<div class="notes-blank-line"></div>
</div>
</div>
{% endif %}
</section>
</div>
</article>
</main>
<script>
// ==========================================
// MOBILE NAVIGATION (3-PUNKTE-MENÜ)
// ==========================================
function toggleMobileMenu(e) {
if (e) e.stopPropagation();
const menu = document.getElementById('cb-mobile-menu');
if (menu) menu.classList.toggle('is-open');
}
document.addEventListener('click', (e) => {
if (!e.target.closest('.mobile-nav-wrapper')) {
const menu = document.getElementById('cb-mobile-menu');
if (menu) menu.classList.remove('is-open');
}
});
// ==========================================
// KOCHMODUS: SCREEN WAKE LOCK & DISPLAY WACHHALTEN
// ==========================================
let wakeLockSentinel = null;
let isCookModeActive = false;
async function requestWakeLock() {
if ('wakeLock' in navigator) {
try {
wakeLockSentinel = await navigator.wakeLock.request('screen');
wakeLockSentinel.addEventListener('release', () => {
if (!isCookModeActive) {
setCookModeUI(false, true);
}
});
return true;
} catch (err) {
console.warn('Wake Lock Anfrage nicht erfolgreich:', err);
return false;
}
}
return false;
}
async function releaseWakeLock() {
if (wakeLockSentinel !== null) {
try {
await wakeLockSentinel.release();
} catch (e) {}
wakeLockSentinel = null;
}
}
async function toggleCookingMode(active) {
isCookModeActive = active;
try {
localStorage.setItem('wtc_cookmode_active', active ? 'true' : 'false');
} catch (e) {}
// Synchronisiere alle Schalter (Desktop-Nav, Banner, Mobil-Menü)
const switches = document.querySelectorAll('.cook-mode-switch-input');
switches.forEach(sw => { sw.checked = active; });
if (active) {
const supported = await requestWakeLock();
setCookModeUI(true, supported);
showToast(supported ? '✓ Kochmodus an: Display bleibt an 💡' : '✓ Kochmodus aktiv');
} else {
await releaseWakeLock();
setCookModeUI(false, true);
showToast('Kochmodus ausgeschaltet');
}
}
function setCookModeUI(active, supported = true) {
if (active) {
document.body.classList.add('cook-mode-on');
} else {
document.body.classList.remove('cook-mode-on');
}
const badge = document.getElementById('cook-mode-badge');
const subText = document.getElementById('cookmode-sub-text');
if (badge) {
if (active) {
badge.textContent = supported ? 'Aktiv 💡' : 'Aktiv';
} else {
badge.textContent = 'Aus';
}
}
if (subText) {
if (active) {
subText.textContent = supported
? 'Display bleibt während des Kochens dauerhaft eingeschaltet 💡'
: 'Display-Wachhalten nicht vom Browser unterstützt (Interaktive Schritte aktiv)';
} else {
subText.textContent = 'Verhindert, dass das Display während des Kochens sperrt';
}
}
}
// Wenn der Nutzer die App/den Tab wechselt und zurückkommt: Wake Lock erneuern!
document.addEventListener('visibilitychange', async () => {
if (isCookModeActive && document.visibilityState === 'visible') {
await requestWakeLock();
}
});
// Gespeicherten Zustand beim Laden wiederherstellen
window.addEventListener('DOMContentLoaded', () => {
try {
const saved = localStorage.getItem('wtc_cookmode_active') === 'true';
if (saved) {
const sw = document.getElementById('cook-mode-toggle');
if (sw) {
sw.checked = true;
toggleCookingMode(true);
}
}
} catch (e) {}
});
// Interaktive Zutaten-Checkliste (zum Abhaken beim Vorbereiten)
function toggleIngredientCheck(el) {
el.classList.toggle('is-checked');
}
// Interaktiver Schritt-Tracker (zum Markieren erledigter Schritte)
function toggleStepCheck(el) {
el.classList.toggle('is-done');
}
// Toast Benachrichtigung
function showToast(msg) {
let toast = document.getElementById('cook-toast');
if (!toast) {
toast = document.createElement('div');
toast.id = 'cook-toast';
toast.className = 'cb-toast';
document.body.appendChild(toast);
}
toast.textContent = msg;
toast.style.display = 'flex';
setTimeout(() => {
toast.style.display = 'none';
}, 2500);
}
</script>
</body>
</html>