diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..810ca3e --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# FlixCooks Firebase Configuration +# Replace these placeholder values with your actual Firebase project settings. + +FIREBASE_API_KEY="AIzaSyYourApiKeyHere" +FIREBASE_AUTH_DOMAIN="flixcooks-your-project-id.firebaseapp.com" +FIREBASE_PROJECT_ID="flixcooks-your-project-id" +FIREBASE_STORAGE_BUCKET="flixcooks-your-project-id.appspot.com" +FIREBASE_MESSAGING_SENDER_ID="123456789012" +FIREBASE_APP_ID="1:123456789012:web:abcdef123456" diff --git a/.gitignore b/.gitignore index c3c7aea..0cfe729 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.idx/* \ No newline at end of file +.idx/* +.env \ No newline at end of file diff --git a/TODO.md b/TODO.md index cc06418..efdfafb 100644 --- a/TODO.md +++ b/TODO.md @@ -12,7 +12,7 @@ Dieses Dokument enthält den aktuellen Entwicklungsstand und detaillierte Aufgab - Fullscreen Overlay-Navigation (`LiquidOverlayMenu` & `SlideUpTextHover`) - Geschmeidiges Scrollverhalten (`LenisSmoothScroll` & `IndexSectionIndicator`) - Layout-Raster (`FloemaLayoutGrid`) & Premium-Buttons -- [ ] **Phase 2: Authentifizierung & Firebase Integration** +- [x] **Phase 2: Authentifizierung & Firebase Integration** - Firebase-Projekt Setup & Anbindung - Newsletter-System (Firebase Firestore) - Benutzerprofile (Registrierung, Login, Favoriten) @@ -63,23 +63,23 @@ Das visuelle Fundament der Website. Die Etablierung des Premium-Designs stellt s ### 🔒 Phase 2: Authentifizierung & Firebase Integration Das Fundament für Personalisierung und Newsletter-Abos über eine sichere Firebase-Anbindung (Auth & Firestore). -- [ ] **Firebase Setup & Initialisierung** - - [ ] Firebase SDK Client-Side Einbindung in `partials/head.php` oder separaten Helper - - [ ] Firebase Config in umweltabhängigen Settings/Variablen auslagern - - [ ] Firestore-Datenbank initialisieren (`subscribers` und `users` Collections) -- [ ] **Premium Login- & Registrierungsseite** - - [ ] Erstellung der Seite `login.php` (und optional `signup.php`) im neuen Design-System-Stil - - [ ] Firebase Authentication (Email/Passwort Login & Registrierung) implementieren - - [ ] Responsive UI mit sanften Error- & Success-Meldungen und Eingabefeldern im Glasmorphismus-Look - - [ ] Client-seitige und Server-seitige Session-Synchronisation (z.B. Firebase Session Tokens an PHP-Session via Ajax senden) -- [ ] **Favoriten-Funktion (Rezept-Bookmarks)** - - [ ] Firestore-Collection `user_favorites` anlegen (Format: `userId` -> Liste von `recipeSlugs`) - - [ ] "Rezept speichern" Button (Herz-Icon) auf den Rezeptkarten und Rezept-Details mit dynamischem Login-Check - - [ ] Mikro-Animationen für das Herz-Icon (Scale up/down, Fülleffekte mit `var(--ease-overshoot)`) -- [ ] **Firebase-gestütztes Newsletter-Abo** - - [ ] Newsletter-Eingabefeld im Footer (`partials/footer.php`) implementieren - - [ ] AJAX-Submit-Handler: E-Mail-Adresse prüfen und in Firestore Collection `subscribers` speichern - - [ ] Elegantes Feedback-UI (Erfolgsmeldung ohne Page-Reload) +- [x] **Firebase Setup & Initialisierung** + - [x] Firebase SDK Client-Side Einbindung in `partials/head.php` or separaten Helper + - [x] Firebase Config in umweltabhängigen Settings/Variablen auslagern + - [x] Firestore-Datenbank initialisieren (`subscribers` und `users` Collections) +- [x] **Premium Login- & Registrierungsseite** + - [x] Erstellung der Seite `login.php` (und optional `signup.php`) im neuen Design-System-Stil + - [x] Firebase Authentication (Email/Passwort Login & Registrierung) implementieren + - [x] Responsive UI mit sanften Error- & Success-Meldungen und Eingabefeldern im Glasmorphismus-Look + - [x] Client-seitige und Server-seitige Session-Synchronisation (z.B. Firebase Session Tokens an PHP-Session via Ajax senden) +- [x] **Favoriten-Funktion (Rezept-Bookmarks)** + - [x] Firestore-Collection `user_favorites` anlegen (Format: `userId` -> Liste von `recipeSlugs`) + - [x] "Rezept speichern" Button (Herz-Icon) auf den Rezeptkarten und Rezept-Details mit dynamischem Login-Check + - [x] Mikro-Animationen für das Herz-Icon (Scale up/down, Fülleffekte mit `var(--ease-overshoot)`) +- [x] **Firebase-gestütztes Newsletter-Abo** + - [x] Newsletter-Eingabefeld im Footer (`partials/footer.php`) implementieren + - [x] AJAX-Submit-Handler: E-Mail-Adresse prüfen und in Firestore Collection `subscribers` speichern + - [x] Elegantes Feedback-UI (Erfolgsmeldung ohne Page-Reload) --- diff --git a/admin.php b/admin.php index 584f48d..31b86b8 100644 --- a/admin.php +++ b/admin.php @@ -204,6 +204,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet $comingSoon = isset($_POST['coming_soon']) && $_POST['coming_soon'] === '1'; $slugOriginal = trim($_POST['slug_original'] ?? ''); + $nutriCal = (int) ($_POST['nutrition_calories'] ?? 0); + $nutriProt = (int) ($_POST['nutrition_protein'] ?? 0); + $nutriCarb = (int) ($_POST['nutrition_carbs'] ?? 0); + $nutriFat = (int) ($_POST['nutrition_fat'] ?? 0); + // keep previous language data if fields left blank while editing $prevEn = $editing['i18n']['en'] ?? []; $prevDe = $editing['i18n']['de'] ?? []; @@ -261,6 +266,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet 'servings' => $servings, 'featured' => $featured, 'coming_soon' => $comingSoon, + 'nutrition' => [ + 'calories' => $nutriCal, + 'protein' => $nutriProt, + 'carbs' => $nutriCarb, + 'fat' => $nutriFat + ], 'i18n' => [ 'en' => [ 'title' => $titleEn, @@ -517,6 +528,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

English

diff --git a/api/session.php b/api/session.php new file mode 100644 index 0000000..9aecee2 --- /dev/null +++ b/api/session.php @@ -0,0 +1,40 @@ + $uid, + 'email' => $email, + 'token' => $token + ]; + echo json_encode(['status' => 'success', 'message' => 'Logged in']); + } else { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Missing UID']); + } +} elseif ($action === 'logout') { + $_SESSION = []; + if (ini_get("session.use_cookies")) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, + $params["path"], $params["domain"], + $params["secure"], $params["httponly"] + ); + } + session_destroy(); + echo json_encode(['status' => 'success', 'message' => 'Logged out']); +} else { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Invalid action']); +} diff --git a/assets/style.css b/assets/style.css index 29d9dc4..2379561 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1735,3 +1735,339 @@ body.nav-open { overflow: hidden; } .feature-grid { grid-template-columns: 1fr; } .coming-features { margin-bottom: 40px; } } + + +/* ╔══════════════════════════════════════════════════════════════╗ + ║ PHASE 2: AUTHENTICATION, FAVORITES & NEWSLETTER STYLES ║ + ╚══════════════════════════════════════════════════════════════╝ */ + +/* ── Premium Heart Buttons ── */ +.card__image { + position: relative; + display: block; +} +.btn-favorite { + position: absolute; + top: 1rem; + right: 1rem; + width: 38px; + height: 38px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.3); + color: var(--muted); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 10; + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + transition: background 0.3s ease, border-color 0.3s ease, transform 0.3s var(--ease-overshoot), color 0.3s ease; +} +[data-theme="dark"] .btn-favorite { + background: rgba(36, 31, 33, 0.7); + border-color: rgba(255, 255, 255, 0.05); +} +.btn-favorite:hover { + transform: scale(1.1); + background: rgba(255, 255, 255, 0.95); + color: var(--ink); +} +[data-theme="dark"] .btn-favorite:hover { + background: rgba(36, 31, 33, 0.95); +} +.btn-favorite svg { + width: 18px; + height: 18px; + transition: stroke 0.3s ease, fill 0.3s ease; + fill: none; + stroke: var(--ink); +} +.btn-favorite.active { + background: var(--surface-2); + border-color: rgba(195, 166, 119, 0.4); +} +.btn-favorite.active svg { + fill: var(--color-accent-gold); + stroke: var(--color-accent-gold); + animation: heartPop 0.45s var(--ease-overshoot) both; +} + +/* For larger recipe layout title heart button */ +.recipe__title-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; + margin-bottom: 0.5rem; + width: 100%; +} +.btn-favorite--large { + position: static; + width: 52px; + height: 52px; + background: var(--surface-2); + border: 1px solid var(--stroke); + flex-shrink: 0; +} +.btn-favorite--large svg { + width: 24px; + height: 24px; +} +@keyframes heartPop { + 0% { transform: scale(1); } + 50% { transform: scale(1.35); } + 100% { transform: scale(1); } +} + +/* ── Guest Favoriting Prompt Modal ── */ +.guest-modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(11, 9, 10, 0.4); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + z-index: 2000; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.4s var(--ease-out-expo); +} +.guest-modal-backdrop.open { + opacity: 1; + pointer-events: auto; +} +.guest-modal-card { + background: var(--surface-2); + border: 1px solid var(--stroke); + box-shadow: var(--shadow); + border-radius: var(--radius); + width: 90%; + max-width: 440px; + padding: 2.5rem; + position: relative; + text-align: center; + transform: translateY(30px); + transition: transform 0.5s var(--ease-overshoot); + display: flex; + flex-direction: column; + align-items: center; + gap: 1.2rem; +} +.guest-modal-backdrop.open .guest-modal-card { + transform: translateY(0); +} +.guest-modal-close { + position: absolute; + top: 1rem; + right: 1.2rem; + background: none; + border: none; + font-size: 1.8rem; + color: var(--muted); + cursor: pointer; + transition: color 0.3s ease; +} +.guest-modal-close:hover { + color: var(--ink); +} +.guest-modal-icon { + font-size: 2.5rem; + line-height: 1; +} +.guest-modal-card h3 { + font-family: var(--font-serif-display); + font-size: 1.8rem; + margin: 0; + color: var(--ink); +} +.guest-modal-card p { + font-size: 0.95rem; + line-height: 1.6; + color: var(--muted); + margin: 0; +} +.guest-modal-actions { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + width: 100%; + margin-top: 0.5rem; +} +.guest-modal-actions .btn-reveal { + width: 100%; + justify-content: center; +} +.guest-modal-actions button { + background: none; + border: none; + font-family: var(--font-sans-clean); + font-size: 0.85rem; + color: var(--muted); + cursor: pointer; +} + +/* ── Footer Newsletter ── */ +.footer-newsletter { + padding-bottom: 2.5rem; + border-bottom: 1px solid var(--stroke); + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; + align-items: center; + margin-bottom: 1rem; + width: 100%; +} +@media (min-width: 768px) { + .footer-newsletter { + grid-template-columns: 1.2fr 1fr; + gap: 3rem; + } +} +.newsletter-content h3 { + font-family: var(--font-serif-display); + font-size: 1.75rem; + margin-bottom: 0.5rem; + color: var(--ink); +} +.newsletter-content p { + color: var(--muted); + font-size: 0.95rem; + line-height: 1.5; + margin: 0; +} +.newsletter-form { + display: flex; + flex-direction: column; + gap: 0.5rem; + position: relative; +} +.newsletter-input-group { + display: flex; + border-bottom: 2px solid var(--stroke); + padding: 0.5rem 0; + transition: border-color 0.3s ease; + position: relative; + align-items: center; +} +.newsletter-input-group:focus-within { + border-color: var(--color-accent-gold); +} +.newsletter-input-group input { + flex: 1; + background: none; + border: none; + font-family: var(--font-sans-clean); + font-size: 1rem; + color: var(--ink); + outline: none; + padding: 0.5rem 0; +} +.newsletter-input-group input::placeholder { + color: var(--color-text-muted); + opacity: 0.7; +} +.newsletter-input-group button { + background: none; + border: none; + color: var(--color-accent-gold); + font-size: 1.5rem; + cursor: pointer; + padding: 0 0.5rem; + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + position: relative; + transition: transform 0.3s var(--ease-overshoot), color 0.3s ease; +} +.newsletter-input-group button:hover { + transform: translateX(4px); +} +.newsletter-input-group button .btn-success-check { + display: none; + color: #10b981; + font-size: 1.2rem; +} +.newsletter-form.success .newsletter-input-group button .btn-text { + display: none; +} +.newsletter-form.success .newsletter-input-group button .btn-success-check { + display: block; +} +.newsletter-form.success .newsletter-input-group button { + transform: none !important; + cursor: default; +} +.newsletter-form.success .newsletter-input-group { + border-color: #10b981; +} +.newsletter-feedback { + font-size: 0.85rem; + margin-top: 0.25rem; + min-height: 1.25rem; + transition: color 0.3s ease; +} +.newsletter-feedback.error { + color: #e02424; +} +.newsletter-feedback.success { + color: #10b981; +} + +/* ── 22. PHASE 3: NUTRITION WIDGET ───────────────────────────── */ +.nutrition-widget { + margin-top: 1.5rem; + margin-bottom: 2rem; + gap: 1.5rem; +} +.nutrition-item { + flex: 1 1 calc(25% - 1.5rem); + min-width: 120px; + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.nutrition-item__header { + display: flex; + justify-content: space-between; + align-items: baseline; + font-family: var(--font-sans-clean); +} +.nutrition-label { + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--muted); +} +.nutrition-value { + font-family: var(--font-serif-display); + font-size: 1.2rem; + font-weight: 700; + color: var(--ink); +} +.nutrition-bar { + width: 100%; + height: 4px; + background-color: var(--ghost); + border-radius: 2px; + overflow: hidden; + position: relative; +} +.nutrition-fill { + height: 100%; + background-color: var(--accent); + border-radius: 2px; + transition: width 1.5s var(--ease-out-expo) 0.2s; + will-change: width; +} diff --git a/config.php b/config.php new file mode 100644 index 0000000..869259c --- /dev/null +++ b/config.php @@ -0,0 +1,46 @@ + getenv('FIREBASE_API_KEY') ?: '', + 'authDomain' => getenv('FIREBASE_AUTH_DOMAIN') ?: '', + 'projectId' => getenv('FIREBASE_PROJECT_ID') ?: '', + 'storageBucket' => getenv('FIREBASE_STORAGE_BUCKET') ?: '', + 'messagingSenderId' => getenv('FIREBASE_MESSAGING_SENDER_ID') ?: '', + 'appId' => getenv('FIREBASE_APP_ID') ?: '', + ]; +} diff --git a/data/recipes.json b/data/recipes.json index 581ffbf..28b7e23 100644 --- a/data/recipes.json +++ b/data/recipes.json @@ -8,6 +8,12 @@ "servings": 2, "featured": false, "coming_soon": false, + "nutrition": { + "calories": 0, + "protein": 0, + "carbs": 0, + "fat": 0 + }, "i18n": { "en": { "title": "Tagliatelle with tomato sauce", @@ -36,16 +42,16 @@ "Optional: Blender" ], "steps": [ - "Pile the flour on your counter, press a well in the center, crack in the eggs. Gradually pull flour into the eggs, then knead 5\u201310 minutes until smooth.", - "Roll the dough to 1\u20132 mm thickness (rolling pin or pasta machine) and cut into tagliatelle.", + "Pile the flour on your counter, press a well in the center, crack in the eggs. Gradually pull flour into the eggs, then knead 5–10 minutes until smooth.", + "Roll the dough to 1–2 mm thickness (rolling pin or pasta machine) and cut into tagliatelle.", "Bring salted water to a boil. Meanwhile, halve tomatoes and sear in olive oil with a pinch of salt until lightly charred.", "Toast the garlic briefly, then blend tomatoes with cream (or crush in the pan) and simmer to thicken slightly.", - "Boil tagliatelle for 2\u20133 minutes, drain, toss with the sauce, and finish with basil and Parmesan." + "Boil tagliatelle for 2–3 minutes, drain, toss with the sauce, and finish with basil and Parmesan." ] }, "de": { "title": "Tagliatelle mit Tomatensauce", - "description": "Einfach, aber richtig lecker; kurze Kochzeit und trotzdem hausgemacht. Das Basisgericht f\u00fcr einen gem\u00fctlichen Abend, stillt den Kohlenhydrat-Hunger ohne versteckte Zus\u00e4tze. Nudeln mit Tomatensauce \u2013 herzhafter geht es kaum.", + "description": "Einfach, aber richtig lecker; kurze Kochzeit und trotzdem hausgemacht. Das Basisgericht für einen gemütlichen Abend, stillt den Kohlenhydrat-Hunger ohne versteckte Zusätze. Nudeln mit Tomatensauce – herzhafter geht es kaum.", "category": "Pasta", "difficulty": "Einfach", "tags": [ @@ -58,7 +64,7 @@ "3 Eier", "10 Cherry-Tomaten", "1/2 Knoblauchzehe", - "1 EL Oliven\u00f6l", + "1 EL Olivenöl", "80 ml Sahne", "Frischer Basilikum", "Parmesan", @@ -70,11 +76,11 @@ "Optional: Mixer" ], "steps": [ - "Mehl auf der Arbeitsfl\u00e4che anh\u00e4ufen, eine Kuhle dr\u00fccken, Eier hineingeben. Mehl nach und nach einarbeiten, dann 5\u201310 Minuten kneten, bis der Teig glatt ist.", - "Teig auf 1\u20132 mm ausrollen (mit Nudelholz oder Nudelmaschine) und in Tagliatelle schneiden.", - "Gesalzenes Wasser aufsetzen; w\u00e4hrenddessen Tomaten halbieren und mit Oliven\u00f6l und einer Prise Salz in der Pfanne anr\u00f6sten, bis R\u00f6stnoten entstehen.", - "Knoblauch kurz mitr\u00f6sten, dann Tomaten mit Sahne p\u00fcrieren (Mixer) oder in der Pfanne zerdr\u00fccken und kurz einkochen lassen.", - "Tagliatelle 2\u20133 Minuten kochen, abgie\u00dfen und mit der Sauce vermengen. Mit Basilikum und Parmesan anrichten." + "Mehl auf der Arbeitsfläche anhäufen, eine Kuhle drücken, Eier hineingeben. Mehl nach und nach einarbeiten, dann 5–10 Minuten kneten, bis der Teig glatt ist.", + "Teig auf 1–2 mm ausrollen (mit Nudelholz oder Nudelmaschine) und in Tagliatelle schneiden.", + "Gesalzenes Wasser aufsetzen; währenddessen Tomaten halbieren und mit Olivenöl und einer Prise Salz in der Pfanne anrösten, bis Röstnoten entstehen.", + "Knoblauch kurz mitrösten, dann Tomaten mit Sahne pürieren (Mixer) oder in der Pfanne zerdrücken und kurz einkochen lassen.", + "Tagliatelle 2–3 Minuten kochen, abgießen und mit der Sauce vermengen. Mit Basilikum und Parmesan anrichten." ] } } @@ -88,6 +94,12 @@ "servings": 2, "featured": false, "coming_soon": true, + "nutrition": { + "calories": 0, + "protein": 0, + "carbs": 0, + "fat": 0 + }, "i18n": { "en": { "title": "Oat-Pancakes", @@ -120,10 +132,16 @@ "servings": 2, "featured": true, "coming_soon": false, + "nutrition": { + "calories": 0, + "protein": 0, + "carbs": 0, + "fat": 0 + }, "i18n": { "en": { "title": "Steak with onion jam", - "description": "A hearty meal after a long day, best enjoyed with a glass of red wine and in good company\u2014because the onion jam still isn\u2019t satisfied on its own.", + "description": "A hearty meal after a long day, best enjoyed with a glass of red wine and in good company—because the onion jam still isn’t satisfied on its own.", "category": "", "difficulty": "Easy", "tags": [ @@ -157,14 +175,14 @@ "Dice the onion", "Cook the potatoes and carrots until al dente", "Sear the steak with butter", - "Once both sides are golden brown, bake for 6 minutes at 160\u00b0C (medium); for medium rare, let it rest after searing both sides for 3 minutes", - "Saut\u00e9 the onion in the steak butter, deglaze with red wine, and add cream once the alcohol has evaporated", + "Once both sides are golden brown, bake for 6 minutes at 160°C (medium); for medium rare, let it rest after searing both sides for 3 minutes", + "Sauté the onion in the steak butter, deglaze with red wine, and add cream once the alcohol has evaporated", "Plate and serve!" ] }, "de": { "title": "Steak mit Zwiebelmarmelade", - "description": "Eine schwere Mahlzeit nach einem langen Tag, am besten zu einem Schluck Rotwein und in Gesellschaft genie\u00dfen, da die Zwiebelmarmelade noch nicht genug davon hat.", + "description": "Eine schwere Mahlzeit nach einem langen Tag, am besten zu einem Schluck Rotwein und in Gesellschaft genießen, da die Zwiebelmarmelade noch nicht genug davon hat.", "category": "", "difficulty": "Einfach", "tags": [ @@ -174,7 +192,7 @@ "ingredients": [ "2 x 250g Rumpsteak", "300gr Kartoffeln", - "1 Gro\u00dfe Rote Zwiebel", + "1 Große Rote Zwiebel", "1/2 Knoblauch-Zehe", "50ml Sahne", "50ml Rotwein", @@ -195,11 +213,11 @@ "Steak 30 Minuten vor dem anbraten raus legen", "Kartoffeln halbieren", "Karotten halbieren", - "Zwiebel w\u00fcrfeln", + "Zwiebel würfeln", "Kartoffeln und Karotten al dente kochen", "Steak anbraten mit Butter", - "sobald beide seiten goldbraun sind f\u00fcr 6 Minuten bei 160grad backen (Medium) f\u00fcr Medium Rare nur ruhen lassen nachdem es von beiden Seiten f\u00fcr 3 Minuten angebraten wurde", - "Zwiebel in Steak-Butter anschwitzen, mit Rotwein abl\u00f6schen und Sahne dazu geben nachdem er Alkohol verd\u00fcnstet ist", + "sobald beide seiten goldbraun sind für 6 Minuten bei 160grad backen (Medium) für Medium Rare nur ruhen lassen nachdem es von beiden Seiten für 3 Minuten angebraten wurde", + "Zwiebel in Steak-Butter anschwitzen, mit Rotwein ablöschen und Sahne dazu geben nachdem er Alkohol verdünstet ist", "Anrichten!" ] } @@ -214,6 +232,12 @@ "servings": 2, "featured": false, "coming_soon": true, + "nutrition": { + "calories": 0, + "protein": 0, + "carbs": 0, + "fat": 0 + }, "i18n": { "en": { "title": "Oat cake", diff --git a/helpers.php b/helpers.php index ab18034..d6b4099 100644 --- a/helpers.php +++ b/helpers.php @@ -1,6 +1,12 @@ " alt="">

-

+
+

+ +

@@ -430,6 +437,11 @@ include __DIR__ . '/partials/head.php'; +

diff --git a/login.php b/login.php new file mode 100644 index 0000000..5fddef4 --- /dev/null +++ b/login.php @@ -0,0 +1,682 @@ + [ + 'page_title' => 'My Profile & Favorites | FlixCooks', + 'meta_desc' => 'Manage your profile, dietary goals, and saved seasonal recipes on FlixCooks.', + 'login' => 'Login', + 'signup' => 'Register', + 'email' => 'Email Address', + 'password' => 'Password', + 'confirm_pass' => 'Confirm Password', + 'goal' => 'Dietary Goal', + 'goal_placeholder' => 'Select your dietary goal', + 'goal_loose' => 'Weight Loss / Low-Carb', + 'goal_gain' => 'Muscle Gain / High-Protein', + 'goal_healthy' => 'Healthy & Balanced', + 'no_account' => "Don't have an account yet?", + 'have_account' => 'Already have an account?', + 'welcome' => 'Welcome back,', + 'your_goal' => 'Your dietary goal', + 'saved_recipes' => 'Your Saved Favorites', + 'no_favorites' => "You haven't saved any recipes yet. Explore our collection and tap the heart icon!", + 'logout' => 'Log Out', + 'err_pass_match' => 'Passwords do not match.', + 'err_fill_fields'=> 'Please fill in all fields.', + ], + 'de' => [ + 'page_title' => 'Mein Profil & Favoriten | FlixCooks', + 'meta_desc' => 'Verwalte dein Profil, deine Ernährungsziele und gespeicherten Rezepte auf FlixCooks.', + 'login' => 'Anmelden', + 'signup' => 'Registrieren', + 'email' => 'E-Mail-Adresse', + 'password' => 'Passwort', + 'confirm_pass' => 'Passwort bestätigen', + 'goal' => 'Ernährungsziel', + 'goal_placeholder' => 'Wähle dein Ernährungsziel', + 'goal_loose' => 'Abnehmen / Low-Carb', + 'goal_gain' => 'Muskelaufbau / High-Protein', + 'goal_healthy' => 'Gesund & Ausgewogen', + 'no_account' => "Noch kein Konto?", + 'have_account' => 'Bereits registriert?', + 'welcome' => 'Willkommen zurück,', + 'your_goal' => 'Dein Ernährungsziel', + 'saved_recipes' => 'Deine gespeicherten Favoriten', + 'no_favorites' => "Du hast noch keine Rezepte gespeichert. Entdecke unsere Küche und klicke auf das Herz-Symbol!", + 'logout' => 'Abmelden', + 'err_pass_match' => 'Die Passwörter stimmen nicht überein.', + 'err_fill_fields'=> 'Bitte fülle alle Felder aus.', + ] +]; + +$t = $copy[$lang]; +$pageTitle = $t['page_title']; +$description = $t['meta_desc']; + +// Check PHP session state +$user = $_SESSION['fc_user'] ?? null; + +$allRecipes = load_recipes(); +$allRecipesLocal = localize_recipes($allRecipes, $lang); + +// Formatter helper for JS usage +$recipesJson = json_encode(array_values(array_map(function($r) use ($lang) { + return [ + 'slug' => $r['slug'], + 'title' => $r['title'], + 'hero' => $r['hero'], + 'difficulty' => $r['difficulty'], + 'total_time' => (int)($r['total_time'] ?? 0), + 'url' => '/recipe.php?slug=' . urlencode($r['slug']) . '&lang=' . $lang + ]; +}, $allRecipesLocal))); + +include __DIR__ . '/partials/head.php'; +include __DIR__ . '/partials/header.php'; +?> + + + +
+
+ + +
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ +
+

+ +

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

+ +

+
+
+ + +
+ + +
+

+ +
+ +
+
+ + +
+
+
+ + + + diff --git a/partials/footer.php b/partials/footer.php index 60919ca..792a0c9 100644 --- a/partials/footer.php +++ b/partials/footer.php @@ -5,6 +5,23 @@ $contactEmail = $settings['imprint'][$langCode]['email'] ?? 'contact@flixcooks.at'; ?>