2026-03-22 22:16:44 +01:00
|
|
|
<?php
|
|
|
|
|
require __DIR__ . '/helpers.php';
|
|
|
|
|
|
|
|
|
|
$lang = (isset($_GET['lang']) && strtolower($_GET['lang']) === 'de') ? 'de' : 'en';
|
|
|
|
|
$copy = [
|
|
|
|
|
'en' => [
|
2026-05-21 11:52:40 +02:00
|
|
|
'not_found' => 'Recipe not found',
|
2026-03-22 22:16:44 +01:00
|
|
|
'not_found_sub' => 'Try searching for something else.',
|
2026-05-21 11:52:40 +02:00
|
|
|
'back_home' => '← Back to all recipes',
|
2026-05-21 14:14:10 +02:00
|
|
|
'nutrition' => 'Nutrition',
|
|
|
|
|
'calories' => 'Calories',
|
|
|
|
|
'protein' => 'Protein',
|
|
|
|
|
'carbs' => 'Carbs',
|
|
|
|
|
'fat' => 'Fat',
|
2026-05-21 11:52:40 +02:00
|
|
|
'ingredients' => 'Ingredients',
|
|
|
|
|
'utensils' => 'Utensils',
|
|
|
|
|
'steps' => 'Steps',
|
|
|
|
|
'also_tasty' => 'Also tasty',
|
|
|
|
|
'more_dishes' => 'More dishes to keep you cooking.',
|
|
|
|
|
'prep' => 'Prep',
|
|
|
|
|
'cook' => 'Cook',
|
|
|
|
|
'total' => 'Total',
|
|
|
|
|
'serves' => 'Serves',
|
|
|
|
|
'level' => 'Level',
|
2026-03-22 22:16:44 +01:00
|
|
|
],
|
|
|
|
|
'de' => [
|
2026-05-21 11:52:40 +02:00
|
|
|
'not_found' => 'Rezept nicht gefunden',
|
2026-03-22 22:16:44 +01:00
|
|
|
'not_found_sub' => 'Versuche eine andere Suche.',
|
2026-05-21 11:52:40 +02:00
|
|
|
'back_home' => '← Zurück zu allen Rezepten',
|
2026-05-21 14:14:10 +02:00
|
|
|
'nutrition' => 'Nährwerte',
|
|
|
|
|
'calories' => 'Kalorien',
|
|
|
|
|
'protein' => 'Protein',
|
|
|
|
|
'carbs' => 'Kohlenhydrate',
|
|
|
|
|
'fat' => 'Fett',
|
2026-05-21 11:52:40 +02:00
|
|
|
'ingredients' => 'Zutaten',
|
|
|
|
|
'utensils' => 'Küchenwerkzeug',
|
|
|
|
|
'steps' => 'Schritte',
|
|
|
|
|
'also_tasty' => 'Auch lecker',
|
|
|
|
|
'more_dishes' => 'Noch mehr zum Nachkochen.',
|
|
|
|
|
'prep' => 'Vorbereitung',
|
|
|
|
|
'cook' => 'Garen',
|
|
|
|
|
'total' => 'Gesamt',
|
|
|
|
|
'serves' => 'Portionen',
|
|
|
|
|
'level' => 'Level',
|
2026-03-22 22:16:44 +01:00
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
$t = $copy[$lang];
|
|
|
|
|
|
2026-05-21 11:52:40 +02:00
|
|
|
$allRecipes = load_recipes();
|
|
|
|
|
$slug = isset($_GET['slug']) ? trim($_GET['slug']) : '';
|
|
|
|
|
$recipeRaw = $slug ? find_recipe_by_slug($allRecipes, $slug) : null;
|
|
|
|
|
$recipe = $recipeRaw ? localize_recipe($recipeRaw, $lang) : null;
|
|
|
|
|
$allRecipesLocal = localize_recipes($allRecipes, $lang);
|
2026-03-22 22:16:44 +01:00
|
|
|
|
|
|
|
|
$langSwitchLabel = $lang === 'de' ? 'EN' : 'DE';
|
2026-05-21 11:52:40 +02:00
|
|
|
$langSwitchHref = lang_url($lang === 'de' ? 'en' : 'de');
|
2026-03-22 22:16:44 +01:00
|
|
|
|
|
|
|
|
if (!$recipe) {
|
|
|
|
|
http_response_code(404);
|
2026-05-21 11:52:40 +02:00
|
|
|
$pageTitle = $t['not_found'] . ' | FlixCooks';
|
2026-03-22 22:16:44 +01:00
|
|
|
$description = $t['not_found_sub'];
|
|
|
|
|
include __DIR__ . '/partials/head.php';
|
|
|
|
|
include __DIR__ . '/partials/header.php';
|
|
|
|
|
?>
|
|
|
|
|
<main class="not-found">
|
|
|
|
|
<h1><?php echo e($t['not_found']); ?></h1>
|
|
|
|
|
<p><?php echo e($t['not_found_sub']); ?></p>
|
2026-05-21 11:52:40 +02:00
|
|
|
<a class="btn-reveal" href="/index.php?lang=<?php echo e($lang); ?>#recipes-start">
|
|
|
|
|
<span class="btn-reveal-text"><?php echo e($t['back_home']); ?></span>
|
|
|
|
|
<span class="btn-reveal-arrow">→</span>
|
|
|
|
|
</a>
|
2026-03-22 22:16:44 +01:00
|
|
|
</main>
|
|
|
|
|
<?php
|
|
|
|
|
include __DIR__ . '/partials/footer.php';
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 11:52:40 +02:00
|
|
|
$pageTitle = e($recipe['title']) . ' | FlixCooks';
|
2026-03-22 22:16:44 +01:00
|
|
|
$description = $recipe['description'] ?? '';
|
|
|
|
|
include __DIR__ . '/partials/head.php';
|
|
|
|
|
include __DIR__ . '/partials/header.php';
|
|
|
|
|
?>
|
|
|
|
|
|
|
|
|
|
<article class="recipe">
|
|
|
|
|
<div class="recipe__hero">
|
|
|
|
|
<img src="<?php echo e($recipe['hero']); ?>" alt="<?php echo e($recipe['title']); ?>">
|
|
|
|
|
<div class="recipe__hero__copy">
|
2026-05-21 11:52:40 +02:00
|
|
|
<?php if (!empty($recipe['category'])): ?>
|
|
|
|
|
<p class="pill pill--gold"><?php echo e($recipe['category']); ?></p>
|
|
|
|
|
<?php endif; ?>
|
2026-05-21 14:14:10 +02:00
|
|
|
<div class="recipe__title-row">
|
|
|
|
|
<h1><?php echo e($recipe['title']); ?></h1>
|
|
|
|
|
<button class="btn-favorite btn-favorite--large" data-slug="<?php echo e($recipe['slug']); ?>" aria-label="Save to favorites">
|
|
|
|
|
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none">
|
|
|
|
|
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-03-22 22:16:44 +01:00
|
|
|
<p class="lede"><?php echo e($recipe['description']); ?></p>
|
|
|
|
|
<div class="meta meta--row">
|
2026-05-21 11:52:40 +02:00
|
|
|
<span><?php echo e($t['prep']); ?>: <?php echo format_minutes((int)$recipe['prep_time']); ?></span>
|
|
|
|
|
<span><?php echo e($t['cook']); ?>: <?php echo format_minutes((int)$recipe['cook_time']); ?></span>
|
|
|
|
|
<span><?php echo e($t['total']); ?>: <?php echo format_minutes((int)$recipe['total_time']); ?></span>
|
2026-03-22 22:16:44 +01:00
|
|
|
<span><?php echo e($t['serves']); ?>: <?php echo e($recipe['servings']); ?></span>
|
|
|
|
|
<span><?php echo e($t['level']); ?>: <?php echo e($recipe['difficulty']); ?></span>
|
|
|
|
|
</div>
|
2026-05-21 11:52:40 +02:00
|
|
|
<a class="btn-reveal" href="/index.php?lang=<?php echo e($lang); ?>#recipes-start">
|
|
|
|
|
<span class="btn-reveal-text"><?php echo e($t['back_home']); ?></span>
|
|
|
|
|
<span class="btn-reveal-arrow">←</span>
|
|
|
|
|
</a>
|
2026-03-22 22:16:44 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="recipe__body">
|
2026-05-21 14:14:10 +02:00
|
|
|
|
|
|
|
|
<?php if (isset($recipe['nutrition']) && ($recipe['nutrition']['calories'] > 0 || $recipe['nutrition']['protein'] > 0 || $recipe['nutrition']['carbs'] > 0 || $recipe['nutrition']['fat'] > 0)): ?>
|
|
|
|
|
<section class="panel reveal-target" id="nutrition-panel">
|
|
|
|
|
<h2><?php echo e($t['nutrition']); ?></h2>
|
|
|
|
|
<div class="g-row nutrition-widget">
|
|
|
|
|
<?php
|
|
|
|
|
$nutri = $recipe['nutrition'];
|
|
|
|
|
$macros = [
|
|
|
|
|
['label' => $t['calories'], 'value' => $nutri['calories'], 'unit' => 'kcal', 'pct' => min(100, round(($nutri['calories']/2000)*100))],
|
|
|
|
|
['label' => $t['protein'], 'value' => $nutri['protein'], 'unit' => 'g', 'pct' => min(100, round(($nutri['protein']/50)*100))],
|
|
|
|
|
['label' => $t['carbs'], 'value' => $nutri['carbs'], 'unit' => 'g', 'pct' => min(100, round(($nutri['carbs']/260)*100))],
|
|
|
|
|
['label' => $t['fat'], 'value' => $nutri['fat'], 'unit' => 'g', 'pct' => min(100, round(($nutri['fat']/70)*100))]
|
|
|
|
|
];
|
|
|
|
|
?>
|
|
|
|
|
<?php foreach ($macros as $m): ?>
|
|
|
|
|
<div class="g-col nutrition-item">
|
|
|
|
|
<div class="nutrition-item__header">
|
|
|
|
|
<span class="nutrition-label"><?php echo e($m['label']); ?></span>
|
|
|
|
|
<span class="nutrition-value"><?php echo e($m['value']); ?><?php echo $m['unit']; ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="nutrition-bar">
|
|
|
|
|
<div class="nutrition-fill" data-width="<?php echo $m['pct']; ?>%" style="width: 0;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
2026-03-22 22:16:44 +01:00
|
|
|
<section class="panel">
|
|
|
|
|
<h2><?php echo e($t['ingredients']); ?></h2>
|
|
|
|
|
<ul class="ingredients">
|
|
|
|
|
<?php foreach ($recipe['ingredients'] as $item): ?>
|
|
|
|
|
<li><?php echo e($item); ?></li>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</ul>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<?php if (!empty($recipe['utensils'])): ?>
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2><?php echo e($t['utensils']); ?></h2>
|
|
|
|
|
<ul class="ingredients">
|
|
|
|
|
<?php foreach ($recipe['utensils'] as $item): ?>
|
|
|
|
|
<li><?php echo e($item); ?></li>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</ul>
|
|
|
|
|
</section>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2><?php echo e($t['steps']); ?></h2>
|
|
|
|
|
<ol class="steps">
|
|
|
|
|
<?php foreach ($recipe['steps'] as $step): ?>
|
|
|
|
|
<li><?php echo e($step); ?></li>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</ol>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<section class="more">
|
2026-05-21 11:52:40 +02:00
|
|
|
<div class="section-header section-header--stack">
|
|
|
|
|
<p class="eyebrow"><?php echo e($t['also_tasty']); ?></p>
|
|
|
|
|
<h3><?php echo e($t['more_dishes']); ?></h3>
|
2026-03-22 22:16:44 +01:00
|
|
|
</div>
|
|
|
|
|
<div class="grid grid--tight">
|
2026-05-21 11:52:40 +02:00
|
|
|
<?php foreach ($allRecipesLocal as $other): ?>
|
2026-03-22 22:16:44 +01:00
|
|
|
<?php if ($other['slug'] === $recipe['slug'] || !empty($other['coming_soon'])) continue; ?>
|
2026-05-21 11:52:40 +02:00
|
|
|
<article class="card card--bare reveal-target">
|
|
|
|
|
<a href="/recipe.php?<?php echo http_build_query(['slug' => $other['slug'], 'lang' => $lang]); ?>" class="card__image">
|
|
|
|
|
<img src="<?php echo e($other['hero']); ?>" alt="<?php echo e($other['title']); ?>" loading="lazy">
|
2026-05-21 14:14:10 +02:00
|
|
|
<button class="btn-favorite" data-slug="<?php echo e($other['slug']); ?>" aria-label="Save to favorites" onclick="event.preventDefault(); event.stopPropagation();">
|
|
|
|
|
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none">
|
|
|
|
|
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
2026-05-21 11:52:40 +02:00
|
|
|
</a>
|
|
|
|
|
<div class="card__body">
|
|
|
|
|
<h4><a href="/recipe.php?<?php echo http_build_query(['slug' => $other['slug'], 'lang' => $lang]); ?>"><?php echo e($other['title']); ?></a></h4>
|
|
|
|
|
<p><?php echo e($other['description']); ?></p>
|
|
|
|
|
<div class="meta">
|
|
|
|
|
<span>⏱ <?php echo format_minutes((int)($other['total_time'] ?? 0)); ?></span>
|
|
|
|
|
<span>🙂 <?php echo e($other['difficulty']); ?></span>
|
|
|
|
|
</div>
|
2026-03-22 22:16:44 +01:00
|
|
|
</div>
|
|
|
|
|
</article>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
<?php include __DIR__ . '/partials/footer.php'; ?>
|
2026-05-21 11:52:40 +02:00
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
// Scroll reveal on recipe page
|
|
|
|
|
if ('IntersectionObserver' in window) {
|
|
|
|
|
var obs = new IntersectionObserver(function(entries) {
|
|
|
|
|
entries.forEach(function(e) {
|
2026-05-21 14:14:10 +02:00
|
|
|
if (e.isIntersecting) {
|
|
|
|
|
e.target.classList.add('revealed');
|
|
|
|
|
obs.unobserve(e.target);
|
|
|
|
|
|
|
|
|
|
// Trigger nutrition bar animations if this is the nutrition panel
|
|
|
|
|
if (e.target.id === 'nutrition-panel') {
|
|
|
|
|
e.target.querySelectorAll('.nutrition-fill').forEach(function(fill) {
|
|
|
|
|
fill.style.width = fill.getAttribute('data-width');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-21 11:52:40 +02:00
|
|
|
});
|
|
|
|
|
}, { threshold: 0.08 });
|
|
|
|
|
document.querySelectorAll('.reveal-target').forEach(function(el) { obs.observe(el); });
|
|
|
|
|
}
|
|
|
|
|
// Lenis init for recipe page
|
|
|
|
|
if (typeof Lenis !== 'undefined') {
|
|
|
|
|
var lenis = new Lenis({ duration: 1.2, smooth: true, smoothTouch: false });
|
|
|
|
|
window.lenis = lenis;
|
|
|
|
|
function raf(time) { lenis.raf(time); requestAnimationFrame(raf); }
|
|
|
|
|
requestAnimationFrame(raf);
|
|
|
|
|
}
|
|
|
|
|
</script>
|