Replace Kochmodus slider with direct toggle button in recipe header & fix Apple keep-awake
This commit is contained in:
@@ -18,13 +18,11 @@
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="cb-nav-actions desktop-nav-actions">
|
||||
<div class="cb-nav-cookmode-toggle no-print" onclick="toggleCookingModeClick(event)" title="Kochmodus: Verhindert automatisches Sperren des Bildschirms" style="cursor: pointer;">
|
||||
<span>👨🍳 Kochmodus</span>
|
||||
<label class="cb-switch cb-switch-sm" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" id="cook-mode-toggle-nav" class="cook-mode-switch-input" onchange="toggleCookingMode(this.checked)">
|
||||
<span class="cb-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" id="cookmode-nav-btn" class="cb-btn cb-btn-outline cb-nav-cookmode-btn no-print" onclick="toggleCookingMode()" title="Kochmodus: Hält das Display beim Kochen aktiv">
|
||||
<span class="cookmode-nav-icon">👨🍳</span>
|
||||
<span>Kochmodus</span>
|
||||
<span id="cookmode-nav-badge" class="cb-badge-pill">Aus</span>
|
||||
</button>
|
||||
{% if not is_shared %}
|
||||
<a href="/cookbook" class="cb-btn cb-btn-outline">
|
||||
<span>📖</span> <span>Alle Rezepte</span>
|
||||
@@ -52,19 +50,16 @@
|
||||
<span>⋮</span>
|
||||
</button>
|
||||
<div id="cb-mobile-menu" class="cb-mobile-dropdown">
|
||||
<div class="cb-mobile-item cb-mobile-cookmode-item" onclick="toggleCookingModeClick(event)" title="Kochmodus: Verhindert automatisches Sperren des Bildschirms">
|
||||
<button type="button" id="cookmode-mobile-btn" class="cb-mobile-item cb-mobile-cookmode-item" onclick="toggleCookingMode()" title="Kochmodus: Hält das Display beim Kochen aktiv">
|
||||
<div class="cookmode-item-left">
|
||||
<span class="cookmode-icon">👨🍳</span>
|
||||
<div class="cookmode-text-box">
|
||||
<span class="cookmode-title">Kochmodus</span>
|
||||
<span class="cookmode-title">Kochmodus (Display bleibt an)</span>
|
||||
<span class="cookmode-status-text" id="cookmode-status-mobile">Display bleibt an</span>
|
||||
</div>
|
||||
</div>
|
||||
<label class="cb-switch cb-switch-sm" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" id="cook-mode-toggle-mobile" class="cook-mode-switch-input" onchange="toggleCookingMode(this.checked)">
|
||||
<span class="cb-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<span id="cookmode-mobile-badge" class="cb-badge-pill">Aus</span>
|
||||
</button>
|
||||
<hr class="cb-mobile-divider">
|
||||
{% if not is_shared %}
|
||||
<button type="button" onclick="openShareModal()" class="cb-mobile-item">
|
||||
@@ -117,14 +112,17 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
<!-- KOCHMODUS TOGGLE BUTTON (Oben rechts auf der Rezeptseite) -->
|
||||
<button type="button"
|
||||
id="cookmode-recipe-btn"
|
||||
class="cookmode-toggle-btn no-print"
|
||||
onclick="toggleCookingMode()"
|
||||
title="Kochmodus umschalten: Hält das Display beim Kochen aktiv">
|
||||
<span class="cookmode-btn-icon">👨🍳</span>
|
||||
<span class="cookmode-btn-title">Kochmodus</span>
|
||||
<span id="cookmode-recipe-badge" class="cookmode-btn-badge">Aus</span>
|
||||
<span class="cookmode-btn-bulb">💡</span>
|
||||
</button>
|
||||
|
||||
<!-- Header (Ohne Kapitelangabe & ohne Quelle im Titel) -->
|
||||
<header class="recipe-header">
|
||||
@@ -534,7 +532,19 @@
|
||||
|
||||
|
||||
|
||||
</main>
|
||||
<!-- Apple/iOS Safari & PWA Keep-Awake Video Element -->
|
||||
<video id="cookmode-keepawake-video"
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
loop
|
||||
muted
|
||||
preload="auto"
|
||||
aria-hidden="true"
|
||||
style="position: fixed; right: 0; bottom: 0; width: 1px; height: 1px; opacity: 0.01; pointer-events: none; z-index: -9999;"
|
||||
src="/static/media/cookmode_nosleep.mp4">
|
||||
</video>
|
||||
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// ==========================================
|
||||
@@ -554,130 +564,166 @@
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// KOCHMODUS: SCREEN WAKE LOCK & NOSLEEP VIDEO FALLBACK
|
||||
// KOCHMODUS: SCREEN WAKE LOCK & iOS VIDEO KEEP-AWAKE
|
||||
// ==========================================
|
||||
let wakeLockSentinel = null;
|
||||
let isCookModeActive = false;
|
||||
|
||||
// 765-Byte stummes H.264 MP4 als universeller Fallback für iOS Safari / Stromsparmodus
|
||||
const NO_SLEEP_MP4 = "data:video/mp4;base64,AAAAHGZ0eXBtcDQyAAAAAG1wNDJpc29tYXZjMQAAADd2bW92AAAAcG12aGQAAAAA1nEwANZxMADAAMAAA+gAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAABidHJhawAAAFx0a2hkAAAAAdZxMADWcTAAAAABAAAAAAAAB+gAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAACbWRpYQAAACRtaGRyAAAAAAAAAAAAAAAAAAB+gAAA+gB4AAAAAAA1aGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAF8bWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADGR1cmwAAAABAAAARXN0YmwAAABTc3RzZAAAAAAAAAABAAAAR2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAHgAeAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//+AAAAAMmF2Y0MBQsAP/v/4AAQcK9AAACWAAAEAAAA8AAAeJAAAAABhdmMxAAAAAAAAAAEAACAAHgAeAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//+AAAAAFHN0dHMAAAAAAAAAAQAAAAEAAAAAAACAAAAAc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAJHN0c3oAAAAAAAAAAAAAAAEAAABkAAAAAAAQc3RjbwAAAAAAAAABAAAAMAAAADBtZGF0AAAAGBAPAAABuQA1AP/wCAAAAGAAAAAAAAAA==";
|
||||
// Video Keep-Awake für iOS Safari / Stromsparmodus / PWA
|
||||
function getKeepAwakeVideo() {
|
||||
return document.getElementById('cookmode-keepawake-video');
|
||||
}
|
||||
|
||||
function getNoSleepVideo() {
|
||||
let v = document.getElementById('cookmode-nosleep-video');
|
||||
if (!v) {
|
||||
v = document.createElement('video');
|
||||
v.id = 'cookmode-nosleep-video';
|
||||
v.setAttribute('playsinline', '');
|
||||
v.setAttribute('webkit-playsinline', '');
|
||||
v.setAttribute('loop', '');
|
||||
v.setAttribute('muted', '');
|
||||
v.muted = true;
|
||||
v.style.cssText = 'position:fixed;width:1px;height:1px;top:-100px;left:-100px;opacity:0.001;pointer-events:none;';
|
||||
v.src = NO_SLEEP_MP4;
|
||||
document.body.appendChild(v);
|
||||
}
|
||||
return v;
|
||||
// Video-Loop Handler gegen AVPlayer Sleep-Suspension
|
||||
function setupVideoLoop(v) {
|
||||
if (!v || v._hasLoopListener) return;
|
||||
v._hasLoopListener = true;
|
||||
v.addEventListener('timeupdate', () => {
|
||||
if (v.currentTime > 0.5) {
|
||||
v.currentTime = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function acquireKeepAwake() {
|
||||
let success = false;
|
||||
// 1. Wake Lock API (Standard für moderne Browser)
|
||||
if ('wakeLock' in navigator) {
|
||||
let wakeLockSuccess = false;
|
||||
|
||||
// 1. Native Screen Wake Lock API (Safari iOS 16.4+, Chrome, Edge)
|
||||
if ('wakeLock' in navigator && navigator.wakeLock && typeof navigator.wakeLock.request === 'function') {
|
||||
try {
|
||||
wakeLockSentinel = await navigator.wakeLock.request('screen');
|
||||
wakeLockSentinel.addEventListener('release', () => {
|
||||
wakeLockSentinel = null;
|
||||
if (isCookModeActive && document.visibilityState === 'visible') {
|
||||
acquireKeepAwake();
|
||||
}
|
||||
});
|
||||
success = true;
|
||||
wakeLockSuccess = true;
|
||||
} catch (err) {
|
||||
console.warn('Wake Lock API abgelehnt oder im Stromsparmodus:', err);
|
||||
console.warn('Screen WakeLock nicht verfügbar oder im Stromsparmodus:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. NoSleep Video Fallback (Garantie für iOS Safari / Stromsparmodus)
|
||||
// 2. Video Keep-Awake Fallback (speziell für iOS Safari, iPadOS & Stromsparmodus)
|
||||
try {
|
||||
const v = getNoSleepVideo();
|
||||
if (v.paused) {
|
||||
const p = v.play();
|
||||
if (p && typeof p.then === 'function') {
|
||||
await p.catch(() => {});
|
||||
const v = getKeepAwakeVideo();
|
||||
if (v) {
|
||||
setupVideoLoop(v);
|
||||
v.muted = true;
|
||||
const playPromise = v.play();
|
||||
if (playPromise !== undefined && typeof playPromise.then === 'function') {
|
||||
await playPromise.catch(e => {
|
||||
console.warn('Video keep-awake play Fehler:', e);
|
||||
});
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} catch (e) {
|
||||
console.warn('NoSleep Video Fallback Fehler:', e);
|
||||
console.warn('Video Keep-Awake Fehler:', e);
|
||||
}
|
||||
|
||||
return success;
|
||||
return wakeLockSuccess;
|
||||
}
|
||||
|
||||
function releaseKeepAwake() {
|
||||
// 1. Release Wake Lock
|
||||
if (wakeLockSentinel !== null) {
|
||||
try { wakeLockSentinel.release(); } catch (e) {}
|
||||
wakeLockSentinel = null;
|
||||
}
|
||||
|
||||
// 2. Pause Video Keep-Awake
|
||||
try {
|
||||
const v = document.getElementById('cookmode-nosleep-video');
|
||||
const v = getKeepAwakeVideo();
|
||||
if (v && !v.paused) {
|
||||
v.pause();
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function toggleCookingMode(active) {
|
||||
isCookModeActive = active;
|
||||
function updateCookModeUI(active) {
|
||||
// Body-Klasse für aktiven Rahmen um das Rezept
|
||||
if (active) {
|
||||
document.body.classList.add('cook-mode-on');
|
||||
} else {
|
||||
document.body.classList.remove('cook-mode-on');
|
||||
}
|
||||
|
||||
// 1. Toggle Button oben rechts im Rezept
|
||||
const recipeBtn = document.getElementById('cookmode-recipe-btn');
|
||||
const recipeBadge = document.getElementById('cookmode-recipe-badge');
|
||||
if (recipeBtn) {
|
||||
if (active) {
|
||||
recipeBtn.classList.add('is-active');
|
||||
if (recipeBadge) recipeBadge.textContent = 'An';
|
||||
} else {
|
||||
recipeBtn.classList.remove('is-active');
|
||||
if (recipeBadge) recipeBadge.textContent = 'Aus';
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Desktop Nav Button
|
||||
const navBtn = document.getElementById('cookmode-nav-btn');
|
||||
const navBadge = document.getElementById('cookmode-nav-badge');
|
||||
if (navBtn) {
|
||||
if (active) {
|
||||
navBtn.classList.add('is-active');
|
||||
if (navBadge) navBadge.textContent = 'An 💡';
|
||||
} else {
|
||||
navBtn.classList.remove('is-active');
|
||||
if (navBadge) navBadge.textContent = 'Aus';
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Mobile Kebab Menü Item
|
||||
const mobileBtn = document.getElementById('cookmode-mobile-btn');
|
||||
const mobileBadge = document.getElementById('cookmode-mobile-badge');
|
||||
const mobileStatus = document.getElementById('cookmode-status-mobile');
|
||||
if (mobileBtn) {
|
||||
if (active) {
|
||||
mobileBtn.classList.add('is-active');
|
||||
if (mobileBadge) mobileBadge.textContent = 'An 💡';
|
||||
if (mobileStatus) mobileStatus.textContent = 'Aktiv 💡 (Display bleibt an)';
|
||||
} else {
|
||||
mobileBtn.classList.remove('is-active');
|
||||
if (mobileBadge) mobileBadge.textContent = 'Aus';
|
||||
if (mobileStatus) mobileStatus.textContent = 'Display bleibt an';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleCookingMode() {
|
||||
isCookModeActive = !isCookModeActive;
|
||||
try {
|
||||
localStorage.setItem('wtc_cookmode_active', active ? 'true' : 'false');
|
||||
localStorage.setItem('wtc_cookmode_active', isCookModeActive ? 'true' : 'false');
|
||||
} catch (e) {}
|
||||
|
||||
// Synchronisiere alle Schalter
|
||||
const switches = document.querySelectorAll('.cook-mode-switch-input');
|
||||
switches.forEach(sw => { sw.checked = active; });
|
||||
updateCookModeUI(isCookModeActive);
|
||||
|
||||
const statusText = document.getElementById('cookmode-status-mobile');
|
||||
|
||||
if (active) {
|
||||
if (isCookModeActive) {
|
||||
await acquireKeepAwake();
|
||||
document.body.classList.add('cook-mode-on');
|
||||
if (statusText) statusText.textContent = 'Aktiv 💡 (Display bleibt an)';
|
||||
showToast('✓ Kochmodus an: Display bleibt an 💡');
|
||||
showToast('✓ Kochmodus aktiv: Display bleibt an 💡');
|
||||
} else {
|
||||
releaseKeepAwake();
|
||||
document.body.classList.remove('cook-mode-on');
|
||||
if (statusText) statusText.textContent = 'Display bleibt an';
|
||||
showToast('Kochmodus ausgeschaltet');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCookingModeClick(e) {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
if (e.target && (e.target.tagName === 'INPUT' || e.target.closest('.cb-switch'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
toggleCookingMode(!isCookModeActive);
|
||||
}
|
||||
|
||||
// Wenn der Nutzer die App/den Tab wechselt und zurückkommt: Wake Lock & Video reaktivieren
|
||||
// Wenn der Nutzer den Tab wechselt und zurückkehrt
|
||||
document.addEventListener('visibilitychange', async () => {
|
||||
if (isCookModeActive && document.visibilityState === 'visible') {
|
||||
await acquireKeepAwake();
|
||||
}
|
||||
});
|
||||
|
||||
// Gespeicherten Zustand beim Laden wiederherstellen
|
||||
// Gespeicherten Zustand beim Laden prüfen
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
const saved = localStorage.getItem('wtc_cookmode_active') === 'true';
|
||||
if (saved) {
|
||||
const switches = document.querySelectorAll('.cook-mode-switch-input');
|
||||
switches.forEach(sw => { sw.checked = true; });
|
||||
document.body.classList.add('cook-mode-on');
|
||||
isCookModeActive = true;
|
||||
const statusText = document.getElementById('cookmode-status-mobile');
|
||||
if (statusText) statusText.textContent = 'Aktiv 💡 (Display bleibt an)';
|
||||
updateCookModeUI(true);
|
||||
|
||||
// User Gesture Listener für automatische Reaktivierung nach Reload
|
||||
const unlockOnGesture = () => {
|
||||
if (isCookModeActive) acquireKeepAwake();
|
||||
document.removeEventListener('touchstart', unlockOnGesture);
|
||||
|
||||
Reference in New Issue
Block a user