diff --git a/.agent/skills/close_feature.json b/.agent/skills/close_feature.json new file mode 100644 index 0000000..5840243 --- /dev/null +++ b/.agent/skills/close_feature.json @@ -0,0 +1,5 @@ +{ + "name": "close_feature", + "description": "Closes a feature branch by merging it into main, verifying functionality with the user, and then pushing and deleting the branch.", + "instructions": "When the user invokes this skill to close a feature branch, execute the following workflow:\n\n1. Identify the current feature branch using `git branch --show-current`. If the user is currently on `main`, ask them to specify which feature branch they want to close.\n2. Use the `run_command` tool to execute `git checkout main` and `git pull origin main`.\n3. Use the `run_command` tool to execute `git merge `.\n4. STOP execution and ask the user to verify that the code/application still works correctly (Control Flow). Ask them to reply with 'approved' or 'yes' when they are ready to proceed.\n5. Once the user confirms, use the `run_command` tool to execute `git push origin main` and `git branch -d `.\n6. Notify the user that the feature branch has been successfully closed." +} diff --git a/AGENT.md b/.agents/rules/AGENT.md similarity index 92% rename from AGENT.md rename to .agents/rules/AGENT.md index dad1936..512c0ff 100644 --- a/AGENT.md +++ b/.agents/rules/AGENT.md @@ -1,3 +1,9 @@ +--- +description: Project workflow rules, GitHub flow, and TODO.md tracking. +alwaysOn: true +always_on: true +glob: "*" +--- ## Branching & Conflict Resolution Strategy (GitHub Flow) This project strictly follows the **GitHub Flow**. All development happens in short-lived feature branches branched off from `main`. Direct commits to `main` are prohibited. diff --git a/.agents/skills/close_feature.json b/.agents/skills/close_feature.json new file mode 100644 index 0000000..5840243 --- /dev/null +++ b/.agents/skills/close_feature.json @@ -0,0 +1,5 @@ +{ + "name": "close_feature", + "description": "Closes a feature branch by merging it into main, verifying functionality with the user, and then pushing and deleting the branch.", + "instructions": "When the user invokes this skill to close a feature branch, execute the following workflow:\n\n1. Identify the current feature branch using `git branch --show-current`. If the user is currently on `main`, ask them to specify which feature branch they want to close.\n2. Use the `run_command` tool to execute `git checkout main` and `git pull origin main`.\n3. Use the `run_command` tool to execute `git merge `.\n4. STOP execution and ask the user to verify that the code/application still works correctly (Control Flow). Ask them to reply with 'approved' or 'yes' when they are ready to proceed.\n5. Once the user confirms, use the `run_command` tool to execute `git push origin main` and `git branch -d `.\n6. Notify the user that the feature branch has been successfully closed." +} diff --git a/TODO.md b/TODO.md index efdfafb..9e0f04d 100644 --- a/TODO.md +++ b/TODO.md @@ -16,11 +16,11 @@ Dieses Dokument enthält den aktuellen Entwicklungsstand und detaillierte Aufgab - Firebase-Projekt Setup & Anbindung - Newsletter-System (Firebase Firestore) - Benutzerprofile (Registrierung, Login, Favoriten) -- [ ] **Phase 3: Nährwerte & Rezepterweiterung** +- [x] **Phase 3: Nährwerte & Rezepterweiterung** - Datenmodell & Admin.php Erweiterung - Visuelles Nährwert-Widget (Floema-Präzision) - Makro-/Diät-Filter & Kategorisierung -- [ ] **Phase 4: Interaktiver Step-by-Step Rezept-Assistent** +- [x] **Phase 4: Interaktiver Step-by-Step Rezept-Assistent** - Admin-Erweiterung für Step-Videos - Fullscreen Cooking-Mode Overlay (mit Lenis scroll lock) - Timers & Looping Video Players pro Zubereitungsschritt @@ -86,37 +86,37 @@ Das Fundament für Personalisierung und Newsletter-Abos über eine sichere Fireb ### 🥗 Phase 3: Nährwerte & Rezepterweiterung Jedes Rezept erhält präzise Makronährstoffe (Kalorien, Proteine, Kohlenhydrate, Fette), die vom Admin gepflegt und dem Nutzer edel präsentiert werden. -- [ ] **Datenmodell & Admin-Panel Erweiterung** - - [ ] `data/recipes.json` Struktur anpassen: Hinzufügen von `nutrition` (`calories`, `protein`, `carbs`, `fat`) - - [ ] `admin.php` Formular erweitern: - - [ ] Eingabefeld für Kalorien (kcal) - - [ ] Eingabefeld für Eiweiß / Protein (g) - - [ ] Eingabefeld für Kohlenhydrate (g) - - [ ] Eingabefeld für Fett (g) - - [ ] Validierung & Speicherung der Nährwertdaten in `admin.php` absichern -- [ ] **Premium Nährwert-Widget (`recipe.php`)** - - [ ] Layout-Integration basierend auf dem `FloemaLayoutGrid` (24-Spalten-Asymmetrie) - - [ ] Visuelle Visualisierung der Makroverteilung (z. B. feine kreisförmige SVG-Fortschrittsanzeigen oder elegante horizontale Balken in Gold `var(--color-accent-gold)`) - - [ ] Hover- und Lade-Mikroanimationen für die Diagramme (Wachstumseffekt mit `var(--ease-out-expo)`) - - [ ] Tooltips mit Prozentangaben am Tagesbedarf +- [x] **Datenmodell & Admin-Panel Erweiterung** + - [x] `data/recipes.json` Struktur anpassen: Hinzufügen von `nutrition` (`calories`, `protein`, `carbs`, `fat`) + - [x] `admin.php` Formular erweitern: + - [x] Eingabefeld für Kalorien (kcal) + - [x] Eingabefeld für Eiweiß / Protein (g) + - [x] Eingabefeld für Kohlenhydrate (g) + - [x] Eingabefeld für Fett (g) + - [x] Validierung & Speicherung der Nährwertdaten in `admin.php` absichern +- [x] **Premium Nährwert-Widget (`recipe.php`)** + - [x] Layout-Integration basierend auf dem `FloemaLayoutGrid` (24-Spalten-Asymmetrie) + - [x] Visuelle Visualisierung der Makroverteilung (z. B. feine kreisförmige SVG-Fortschrittsanzeigen oder elegante horizontale Balken in Gold `var(--color-accent-gold)`) + - [x] Hover- und Lade-Mikroanimationen für die Diagramme (Wachstumseffekt mit `var(--ease-out-expo)`) + - [x] Tooltips mit Prozentangaben am Tagesbedarf --- ### ⏱️ Phase 4: Interaktiver Step-by-Step Rezept-Assistent Ein immersiver Kochmodus, der Anwendern Schritt-für-Schritt durch die Zubereitung führt – inklusive Videoanleitungen und integrierten Timern. -- [ ] **Video-Verknüpfung im Admin-Panel** - - [ ] Rezeptstruktur in `data/recipes.json` erweitern, damit jeder Arbeitsschritt (`steps`) eine optionale `video_url` (MP4 aus Firebase Storage oder extern) besitzen kann - - [ ] `admin.php` erweitern, um Video-URLs pro Einzelschritt einzugeben -- [ ] **Fullscreen Cooking-Mode Overlay** - - [ ] Trigger-Button "Kochmodus starten" (`CapitoliumRevealButton` Stil) auf `recipe.php` einbauen - - [ ] Fullscreen Overlay mit flüssigem `clip-path` Öffnungseffekt (analog zu `LiquidOverlayMenu`) - - [ ] Lenis Scroll-Lock während der Kochmodus aktiv ist, um störendes Scrollen im Hintergrund zu verhindern -- [ ] **Interaktives Koch-Interface** - - [ ] Schritt-für-Schritt Slider/Karussell (Tastatur- & Touch-Navigation) - - [ ] Fortschrittsanzeige (z.B. "Schritt 3 von 8" + Fortschrittsbalken) - - [ ] Autolooping Video-Player: Zeigt das Kurzvideo zum aktuellen Zubereitungsschritt, stummgeschaltet und nahtlos wiederholend - - [ ] Integrierte Koch-Timer: Falls im Schritt eine Zeit angegeben ist, wird automatisch ein interaktiver Timer mit Start/Pause/Reset und akustischem Endsignal eingebunden +- [x] **Video-Verknüpfung im Admin-Panel** + - [x] Rezeptstruktur in `data/recipes.json` erweitern, damit jeder Arbeitsschritt (`steps`) eine optionale `video_url` (MP4 aus Firebase Storage oder extern) besitzen kann + - [x] `admin.php` erweitern, um Video-URLs pro Einzelschritt einzugeben +- [x] **Fullscreen Cooking-Mode Overlay** + - [x] Trigger-Button "Kochmodus starten" (`CapitoliumRevealButton` Stil) auf `recipe.php` einbauen + - [x] Fullscreen Overlay mit flüssigem `clip-path` Öffnungseffekt (analog zu `LiquidOverlayMenu`) + - [x] Lenis Scroll-Lock während der Kochmodus aktiv ist, um störendes Scrollen im Hintergrund zu verhindern +- [x] **Interaktives Koch-Interface** + - [x] Schritt-für-Schritt Slider/Karussell (Tastatur- & Touch-Navigation) + - [x] Fortschrittsanzeige (z.B. "Schritt 3 von 8" + Fortschrittsbalken) + - [x] Autolooping Video-Player: Zeigt das Kurzvideo zum aktuellen Zubereitungsschritt, stummgeschaltet und nahtlos wiederholend + - [x] Integrierte Koch-Timer: Falls im Schritt eine Zeit angegeben ist, wird automatisch ein interaktiver Timer mit Start/Pause/Reset und akustischem Endsignal eingebunden --- diff --git a/admin.php b/admin.php index 31b86b8..1902a51 100644 --- a/admin.php +++ b/admin.php @@ -178,6 +178,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet // helpers $parse_csv = fn($text) => array_values(array_filter(array_map('trim', explode(',', $text ?? '')), 'strlen')); $parse_lines = fn($text) => array_values(array_filter(array_map('trim', preg_split('/\\r?\\n/', $text ?? '')), 'strlen')); + $parse_lines_exact = fn($text) => array_map('trim', preg_split('/\\r?\\n/', $text ?? '')); $titleEn = trim($_POST['title_en'] ?? ''); $titleDe = trim($_POST['title_de'] ?? ''); @@ -200,6 +201,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet $utensilsDe = $parse_lines($_POST['utensils_de'] ?? ''); $stepsEn = $parse_lines($_POST['steps_en'] ?? ''); $stepsDe = $parse_lines($_POST['steps_de'] ?? ''); + $stepVideosEn = $parse_lines_exact($_POST['step_videos_en'] ?? ''); + $stepVideosDe = $parse_lines_exact($_POST['step_videos_de'] ?? ''); + $stepTimersEn = $parse_lines_exact($_POST['step_timers_en'] ?? ''); + $stepTimersDe = $parse_lines_exact($_POST['step_timers_de'] ?? ''); $featured = isset($_POST['featured']) && $_POST['featured'] === '1'; $comingSoon = isset($_POST['coming_soon']) && $_POST['coming_soon'] === '1'; $slugOriginal = trim($_POST['slug_original'] ?? ''); @@ -229,6 +234,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet $utensilsDe = !empty($utensilsDe) ? $utensilsDe : ($prevDe['utensils'] ?? []); $stepsEn = !empty($stepsEn) ? $stepsEn : ($prevEn['steps'] ?? []); $stepsDe = !empty($stepsDe) ? $stepsDe : ($prevDe['steps'] ?? []); + $stepVideosEn = !empty($stepVideosEn) ? $stepVideosEn : ($prevEn['step_videos'] ?? []); + $stepVideosDe = !empty($stepVideosDe) ? $stepVideosDe : ($prevDe['step_videos'] ?? []); + $stepTimersEn = !empty($stepTimersEn) ? $stepTimersEn : ($prevEn['step_timers'] ?? []); + $stepTimersDe = !empty($stepTimersDe) ? $stepTimersDe : ($prevDe['step_timers'] ?? []); if ($titleEn === '') { $message = 'English title is required.'; @@ -282,6 +291,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet 'ingredients' => $ingredientsEn, 'utensils' => $utensilsEn, 'steps' => $stepsEn, + 'step_videos' => $stepVideosEn, + 'step_timers' => $stepTimersEn, ], 'de' => [ 'title' => $titleDe ?: $titleEn, @@ -292,6 +303,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet 'ingredients' => !empty($ingredientsDe) ? $ingredientsDe : $ingredientsEn, 'utensils' => !empty($utensilsDe) ? $utensilsDe : $utensilsEn, 'steps' => !empty($stepsDe) ? $stepsDe : $stepsEn, + 'step_videos' => !empty($stepVideosDe) ? $stepVideosDe : $stepVideosEn, + 'step_timers' => !empty($stepTimersDe) ? $stepTimersDe : $stepTimersEn, ], ], ]; @@ -578,6 +591,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet + + + +

Deutsch

@@ -610,6 +627,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet + + + + diff --git a/assets/style.css b/assets/style.css index 2379561..66813bc 100644 --- a/assets/style.css +++ b/assets/style.css @@ -2071,3 +2071,145 @@ body.nav-open { overflow: hidden; } transition: width 1.5s var(--ease-out-expo) 0.2s; will-change: width; } + +/* ========================================================================== + Cooking Mode Overlay + ========================================================================== */ + +.cooking-overlay { + position: fixed; + inset: 0; + z-index: 9999; + background: var(--surface-1); + color: var(--ink); + display: flex; + flex-direction: column; + clip-path: polygon(0 100%, 100% 100%, 100% 100%, 0% 100%); + transition: clip-path 0.6s var(--ease-out-expo); +} +.cooking-overlay.is-open { + clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); +} +.btn-close-cooking { + position: absolute; + top: 24px; + right: 24px; + background: rgba(0,0,0,0.5); + color: #fff; + border: none; + width: 48px; + height: 48px; + border-radius: 50%; + cursor: pointer; + z-index: 10; + display: grid; + place-items: center; + backdrop-filter: blur(8px); +} +.cooking-slider { + flex: 1; + position: relative; + overflow: hidden; +} +.cooking-slide { + position: absolute; + inset: 0; + opacity: 0; + visibility: hidden; + transition: opacity 0.4s ease, visibility 0.4s; + display: flex; + flex-direction: column; + justify-content: flex-end; +} +.cooking-slide.active { + opacity: 1; + visibility: visible; +} +.cooking-video { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 0; +} +.cooking-content { + position: relative; + z-index: 1; + padding: 48px 24px; + background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.3) 60%, transparent 100%); + color: #fff; +} +.cooking-step-indicator { + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 2px; + font-size: 0.8rem; + margin-bottom: 8px; + opacity: 0.8; +} +.cooking-step-text { + font-family: var(--font-display); + font-size: clamp(1.5rem, 4vw, 2.5rem); + margin: 0 0 24px 0; + max-width: 800px; + line-height: 1.2; +} +.cooking-timer { + display: flex; + align-items: center; + gap: 16px; + background: rgba(255,255,255,0.1); + backdrop-filter: blur(12px); + padding: 12px 24px; + border-radius: 100px; + width: fit-content; + border: 1px solid rgba(255,255,255,0.2); +} +.timer-display { + font-family: var(--font-mono); + font-size: 1.5rem; + font-variant-numeric: tabular-nums; +} +.timer-controls { + display: flex; + gap: 8px; +} +.btn-timer { + background: var(--accent); + color: #fff; + border: none; + padding: 6px 12px; + border-radius: 20px; + font-weight: 600; + cursor: pointer; + font-size: 0.9rem; +} +.btn-timer.reset { + background: rgba(255,255,255,0.2); +} +.cooking-nav { + position: absolute; + bottom: 48px; + right: 24px; + z-index: 2; + display: flex; + gap: 12px; +} +.btn-circle { + width: 56px; + height: 56px; + border-radius: 50%; + border: 1px solid rgba(255,255,255,0.3); + background: rgba(0,0,0,0.5); + color: #fff; + font-size: 1.5rem; + cursor: pointer; + backdrop-filter: blur(8px); + display: grid; + place-items: center; +} +.btn-circle:disabled { + opacity: 0.3; + cursor: not-allowed; +} diff --git a/index.php b/index.php index 143c00d..1a4a3b5 100644 --- a/index.php +++ b/index.php @@ -533,7 +533,13 @@ include __DIR__ . '/partials/head.php'; setTimeout(tickPreloader, 65 + Math.random() * 80); } } - tickPreloader(); + if (sessionStorage.getItem('flixcooks_preloader_seen')) { + if (preEl && preEl.parentNode) preEl.parentNode.removeChild(preEl); + done = true; + } else { + sessionStorage.setItem('flixcooks_preloader_seen', 'true'); + tickPreloader(); + } /* ─────────────────────────────────────────────────────────────── diff --git a/recipe.php b/recipe.php index 2b52aed..2aeaca2 100644 --- a/recipe.php +++ b/recipe.php @@ -162,7 +162,12 @@ include __DIR__ . '/partials/header.php';
-

+
+

+ +
  1. @@ -202,6 +207,46 @@ include __DIR__ . '/partials/header.php';
+ + + +