[ 'en' => [ 'landing_eyebrow' => 'Seasonal · Unfussy · Ridiculously tasty', 'landing_line_1' => 'Cook better', 'landing_line_2' => 'weeknights.', 'landing_line_3' => 'Brighter weekends.', 'landing_cta' => 'Explore recipes', 'coming_soon_badge' => 'Coming soon', 'coming_soon_suffix' => 'in progress', 'footer_designed_by' => 'Website designed by Felix Brockers', 'footer_contact_label' => 'Contact me', 'home_title' => 'FlixCooks | Modern food blog for busy home cooks', 'home_description' => 'Fresh, fast recipes with clear steps, smart prep notes, and big flavor. Built for Hetzner web hosting in plain PHP.', 'coming_feature_1' => 'Nutrition info & recipe suggestions tailored to your goals', 'coming_feature_2' => 'Send your grocery list straight to your phone', 'coming_feature_3' => 'Step-by-step recipe assistant with videos', ], 'de' => [ 'landing_eyebrow' => 'Saisonal · Unkompliziert · Richtig lecker', 'landing_line_1' => 'Besser kochen', 'landing_line_2' => 'unter der Woche.', 'landing_line_3' => 'Glänzen am Wochenende.', 'landing_cta' => 'Rezepte entdecken', 'coming_soon_badge' => 'Bald verfügbar', 'coming_soon_suffix' => 'in Arbeit', 'footer_designed_by' => 'Website designed by Felix Brockers', 'footer_contact_label' => 'Kontakt', 'home_title' => 'FlixCooks | Moderner Foodblog für schnelle Alltagsküche', 'home_description' => 'Frische, schnelle Rezepte mit klaren Schritten und großem Geschmack. Gebaut für Hetzner Webhosting in einfachem PHP.', 'coming_feature_1' => 'Nährwerte & Rezepte Vorschlag individuell angepasst auf Ernährungsziel', 'coming_feature_2' => 'Einkaufsliste ans Handy senden', 'coming_feature_3' => 'Step-by-Step Rezepte Assistent mit Videos', ], ], 'imprint' => [ 'de' => [ 'owner_name' => 'FlixCooks (bitte anpassen)', 'address' => 'Straße Hausnummer, PLZ Ort, Österreich', 'email' => 'contact@flixcooks.at', 'phone' => '+43 660 0000000', 'legal_form' => 'Kleines Impressum; kein Firmenbucheintrag/UID hinterlegt.', 'business_purpose' => 'Foodblog & Rezeptmarketing.', 'wko_membership' => '', 'authority' => '', 'uid' => '', 'odr' => 'https://ec.europa.eu/consumers/odr', 'last_updated' => $today, ], 'en' => [ 'owner_name' => 'FlixCooks (please update)', 'address' => 'Street number, ZIP City, Austria', 'email' => 'contact@flixcooks.at', 'phone' => '+43 660 0000000', 'legal_form' => 'Small website notice; no commercial register/UID provided.', 'business_purpose' => 'Food blog & recipe marketing.', 'wko_membership' => '', 'authority' => '', 'uid' => '', 'odr' => 'https://consumer-redress.ec.europa.eu/', 'last_updated' => $today, ], ], 'privacy' => [ 'de' => [ 'controller' => 'FlixCooks (bitte anpassen)', 'contact_email' => 'contact@flixcooks.at', 'contact_phone' => '+43 660 0000000', 'address' => 'Straße Hausnummer, PLZ Ort, Österreich', 'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Deutschland', 'log_retention_days' => 30, 'cookie_statement' => 'Keine Tracking- oder Marketing-Cookies; nur technisch notwendige.', 'purposes' => 'Betrieb und Bereitstellung der Website, Beantwortung von Kontaktanfragen.', 'last_updated' => $today, ], 'en' => [ 'controller' => 'FlixCooks (please update)', 'contact_email' => 'contact@flixcooks.at', 'contact_phone' => '+43 660 0000000', 'address' => 'Street number, ZIP City, Austria', 'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany', 'log_retention_days' => 30, 'cookie_statement' => 'No tracking or marketing cookies; only technically necessary cookies.', 'purposes' => 'Operating and providing the website, responding to contact requests.', 'last_updated' => $today, ], ], ]; } /** * Load site-wide settings for legal pages, merging with defaults. */ function load_site_settings(): array { $defaults = default_site_settings(); $path = __DIR__ . '/data/site.json'; if (!file_exists($path)) { return $defaults; } $raw = file_get_contents($path); $data = json_decode($raw, true); if (!is_array($data)) { return $defaults; } return array_replace_recursive($defaults, $data); } /** * Persist site-wide settings for legal pages. */ function save_site_settings(array $settings): bool { $path = __DIR__ . '/data/site.json'; $json = json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); return (bool) file_put_contents($path, $json); } function slugify(string $text): string { $text = strtolower($text); $text = preg_replace('~[^a-z0-9]+~', '-', $text); $text = trim($text, '-'); return $text ?: uniqid('recipe-'); } function find_recipe_by_slug(array $recipes, string $slug): ?array { foreach ($recipes as $recipe) { if (($recipe['slug'] ?? '') === $slug) { return $recipe; } } return null; } function localize_recipe(array $recipe, string $lang): array { $localized = $recipe; $i18n = $recipe['i18n'][$lang] ?? ($recipe['i18n']['en'] ?? []); foreach ($i18n as $key => $value) { $localized[$key] = $value; } return $localized; } function localize_recipes(array $recipes, string $lang): array { return array_map(function ($recipe) use ($lang) { return localize_recipe($recipe, $lang); }, $recipes); } function filter_recipes(array $recipes, ?string $query = null, ?string $tag = null, string $lang = 'en'): array { $query = $query ? strtolower($query) : null; $tag = $tag ? strtolower($tag) : null; $localized = localize_recipes($recipes, $lang); return array_values(array_filter($localized, function ($recipe) use ($query, $tag) { $matchesQuery = true; if ($query) { $haystack = strtolower(($recipe['title'] ?? '') . ' ' . ($recipe['description'] ?? '')); $matchesQuery = strpos($haystack, $query) !== false; } $matchesTag = true; if ($tag) { $tags = array_map('strtolower', $recipe['tags'] ?? []); $matchesTag = in_array($tag, $tags, true); } return $matchesQuery && $matchesTag; })); } function format_minutes(int $minutes): string { if ($minutes < 60) { return $minutes . ' min'; } $hours = intdiv($minutes, 60); $mins = $minutes % 60; return $hours . ' hr' . ($hours > 1 ? 's ' : ' ') . ($mins ? $mins . ' min' : ''); } function e(?string $value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } function normalize_asset_path(string $path): string { if (!str_starts_with($path, '/assets/')) { return $path; } $absolute = __DIR__ . $path; if (file_exists($absolute)) { return $path; } $dir = dirname($absolute); $target = basename($absolute); if (!is_dir($dir)) { return $path; } foreach (scandir($dir) ?: [] as $entry) { if (strcasecmp($entry, $target) === 0) { return rtrim(dirname($path), '/') . '/' . $entry; } } return $path; } /** * Build a URL to the current path with a specific language parameter, preserving other query params. */ function lang_url(string $lang): string { $params = $_GET; $params['lang'] = $lang; $path = strtok($_SERVER['REQUEST_URI'], '?') ?: '/'; return $path . '?' . http_build_query($params); }