feat: implement phase 4 interactive cooking mode and workspace rules
This commit is contained in:
+160
-1
@@ -162,7 +162,12 @@ include __DIR__ . '/partials/header.php';
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="panel">
|
||||
<h2><?php echo e($t['steps']); ?></h2>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;">
|
||||
<h2 style="margin: 0;"><?php echo e($t['steps']); ?></h2>
|
||||
<button id="btn-start-cooking" class="btn-reveal">
|
||||
<span class="btn-reveal-text"><?php echo $lang === 'de' ? 'Kochmodus starten' : 'Start Cooking Mode'; ?></span>
|
||||
</button>
|
||||
</div>
|
||||
<ol class="steps">
|
||||
<?php foreach ($recipe['steps'] as $step): ?>
|
||||
<li><?php echo e($step); ?></li>
|
||||
@@ -202,6 +207,46 @@ include __DIR__ . '/partials/header.php';
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<?php if (!empty($recipe['steps'])): ?>
|
||||
<div id="cooking-overlay" class="cooking-overlay" hidden>
|
||||
<div class="cooking-slider" id="cooking-slider">
|
||||
<?php foreach ($recipe['steps'] as $idx => $stepText):
|
||||
$vid = $recipe['step_videos'][$idx] ?? '';
|
||||
$timer = (int)($recipe['step_timers'][$idx] ?? 0);
|
||||
?>
|
||||
<div class="cooking-slide <?php echo $idx === 0 ? 'active' : ''; ?>" data-index="<?php echo $idx; ?>">
|
||||
<?php if ($vid): ?>
|
||||
<video class="cooking-video" src="<?php echo e($vid); ?>" loop muted playsinline <?php echo $idx === 0 ? 'autoplay' : ''; ?>></video>
|
||||
<?php endif; ?>
|
||||
<div class="cooking-content">
|
||||
<p class="cooking-step-indicator"><?php echo $lang === 'de' ? 'Schritt' : 'Step'; ?> <?php echo $idx + 1; ?> / <?php echo count($recipe['steps']); ?></p>
|
||||
<h3 class="cooking-step-text"><?php echo e($stepText); ?></h3>
|
||||
|
||||
<?php if ($timer > 0): ?>
|
||||
<div class="cooking-timer" data-time="<?php echo $timer * 60; ?>">
|
||||
<span class="timer-display"><?php echo sprintf('%02d:00', $timer); ?></span>
|
||||
<div class="timer-controls">
|
||||
<button class="btn-timer start"><?php echo $lang === 'de' ? 'Start' : 'Start'; ?></button>
|
||||
<button class="btn-timer pause" hidden><?php echo $lang === 'de' ? 'Pause' : 'Pause'; ?></button>
|
||||
<button class="btn-timer reset"><?php echo $lang === 'de' ? 'Reset' : 'Reset'; ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="cooking-nav">
|
||||
<button id="btn-prev-step" class="btn-circle" disabled>←</button>
|
||||
<button id="btn-next-step" class="btn-circle">→</button>
|
||||
<button id="btn-close-cooking" class="btn-circle" style="background: rgba(220, 38, 38, 0.5); border-color: rgba(220, 38, 38, 0.8);" aria-label="Close">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2.5" fill="none"><path d="M18 6L6 18M6 6l12 12"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include __DIR__ . '/partials/footer.php'; ?>
|
||||
|
||||
<script>
|
||||
@@ -231,4 +276,118 @@ if (typeof Lenis !== 'undefined') {
|
||||
function raf(time) { lenis.raf(time); requestAnimationFrame(raf); }
|
||||
requestAnimationFrame(raf);
|
||||
}
|
||||
|
||||
// Cooking Mode Logic
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btnStart = document.getElementById('btn-start-cooking');
|
||||
const btnClose = document.getElementById('btn-close-cooking');
|
||||
const overlay = document.getElementById('cooking-overlay');
|
||||
|
||||
if (!btnStart || !overlay) return;
|
||||
|
||||
const slides = overlay.querySelectorAll('.cooking-slide');
|
||||
const btnPrev = document.getElementById('btn-prev-step');
|
||||
const btnNext = document.getElementById('btn-next-step');
|
||||
let currentSlide = 0;
|
||||
|
||||
// Audio context for timer
|
||||
const beep = new Audio('data:audio/mp3;base64,//NExAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); // Fallback empty if needed, or simple beep
|
||||
|
||||
function updateSlider() {
|
||||
slides.forEach((slide, idx) => {
|
||||
if (idx === currentSlide) {
|
||||
slide.classList.add('active');
|
||||
const vid = slide.querySelector('video');
|
||||
if (vid) vid.play().catch(e=>console.log(e));
|
||||
} else {
|
||||
slide.classList.remove('active');
|
||||
const vid = slide.querySelector('video');
|
||||
if (vid) { vid.pause(); vid.currentTime = 0; }
|
||||
}
|
||||
});
|
||||
btnPrev.disabled = currentSlide === 0;
|
||||
btnNext.disabled = currentSlide === slides.length - 1;
|
||||
}
|
||||
|
||||
btnStart.addEventListener('click', () => {
|
||||
overlay.hidden = false;
|
||||
// trigger clip-path animation by adding a class in next frame
|
||||
requestAnimationFrame(() => overlay.classList.add('is-open'));
|
||||
if (window.lenis) window.lenis.stop();
|
||||
updateSlider();
|
||||
});
|
||||
|
||||
btnClose.addEventListener('click', () => {
|
||||
overlay.classList.remove('is-open');
|
||||
setTimeout(() => {
|
||||
overlay.hidden = true;
|
||||
if (window.lenis) window.lenis.start();
|
||||
slides.forEach(s => {
|
||||
const vid = s.querySelector('video');
|
||||
if(vid) vid.pause();
|
||||
});
|
||||
}, 600); // match transition duration
|
||||
});
|
||||
|
||||
btnNext.addEventListener('click', () => {
|
||||
if (currentSlide < slides.length - 1) { currentSlide++; updateSlider(); }
|
||||
});
|
||||
|
||||
btnPrev.addEventListener('click', () => {
|
||||
if (currentSlide > 0) { currentSlide--; updateSlider(); }
|
||||
});
|
||||
|
||||
// Timers
|
||||
document.querySelectorAll('.cooking-timer').forEach(timerEl => {
|
||||
let time = parseInt(timerEl.dataset.time, 10);
|
||||
let initialTime = time;
|
||||
let interval = null;
|
||||
const display = timerEl.querySelector('.timer-display');
|
||||
const btnStartTimer = timerEl.querySelector('.start');
|
||||
const btnPauseTimer = timerEl.querySelector('.pause');
|
||||
const btnResetTimer = timerEl.querySelector('.reset');
|
||||
|
||||
function updateDisplay() {
|
||||
const m = Math.floor(time / 60);
|
||||
const s = time % 60;
|
||||
display.textContent = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
btnStartTimer.addEventListener('click', () => {
|
||||
btnStartTimer.hidden = true;
|
||||
btnPauseTimer.hidden = false;
|
||||
interval = setInterval(() => {
|
||||
if (time > 0) {
|
||||
time--;
|
||||
updateDisplay();
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
btnPauseTimer.hidden = true;
|
||||
// Play a simple beep
|
||||
try {
|
||||
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const osc = ctx.createOscillator();
|
||||
osc.connect(ctx.destination);
|
||||
osc.start();
|
||||
setTimeout(() => osc.stop(), 500);
|
||||
} catch(e) {}
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
btnPauseTimer.addEventListener('click', () => {
|
||||
clearInterval(interval);
|
||||
btnStartTimer.hidden = false;
|
||||
btnPauseTimer.hidden = true;
|
||||
});
|
||||
|
||||
btnResetTimer.addEventListener('click', () => {
|
||||
clearInterval(interval);
|
||||
time = initialTime;
|
||||
updateDisplay();
|
||||
btnStartTimer.hidden = false;
|
||||
btnPauseTimer.hidden = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user