Adjust recipe detail view
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m18s
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m18s
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { RECIPES, INGREDIENTS, PRODUCTS } from '../data/catalog.js?v=8';
|
import { RECIPES, INGREDIENTS, PRODUCTS } from '../data/catalog.js?v=8';
|
||||||
import { MEAL_SLOTS } from '../planner/mealSlots.js';
|
|
||||||
import { createIngredientCardController, getIngredientCardHTML } from '../ui/ingredientCard.js?v=20260410-107';
|
import { createIngredientCardController, getIngredientCardHTML } from '../ui/ingredientCard.js?v=20260410-107';
|
||||||
|
|
||||||
function escapeHtml(s) {
|
function escapeHtml(s) {
|
||||||
@@ -10,7 +9,6 @@ function escapeHtml(s) {
|
|||||||
.replace(/"/g, '"');
|
.replace(/"/g, '"');
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotLabelMap = Object.fromEntries(MEAL_SLOTS.map((s) => [s.id, s.label]));
|
|
||||||
const RD_THEME = Object.freeze({
|
const RD_THEME = Object.freeze({
|
||||||
surface: '#393937',
|
surface: '#393937',
|
||||||
surfaceSoft: '#2f2f2d',
|
surfaceSoft: '#2f2f2d',
|
||||||
@@ -31,72 +29,42 @@ function forceBgBorder(bg, border) {
|
|||||||
return `${forceBg(bg)} border:1px solid ${border} !important;`;
|
return `${forceBg(bg)} border:1px solid ${border} !important;`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTagChip(label) {
|
|
||||||
return `<span class="px-3 py-1 rounded-full text-[11px] font-semibold inline-flex items-center" style="${forceBgBorder(RD_THEME.surfaceSoft, RD_THEME.border)} color:${RD_THEME.textSecondary} !important;">${escapeHtml(label)}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTabButtonState(btn, active) {
|
|
||||||
if (!btn) return;
|
|
||||||
btn.style.color = active ? RD_THEME.textPrimary : RD_THEME.textMuted;
|
|
||||||
btn.style.borderBottomColor = active ? RD_THEME.borderStrong : 'transparent';
|
|
||||||
btn.style.fontWeight = active ? '600' : '500';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRecipeDetailHTML() {
|
export function getRecipeDetailHTML() {
|
||||||
return `
|
return `
|
||||||
<style>
|
<div id="recipe-detail-view" class="absolute inset-0 bg-[#2d2e2b] z-30 transition-all duration-300 ease-in-out translate-x-full opacity-0 pointer-events-none overflow-hidden" style="background:#2d2e2b !important; background-image:none !important;">
|
||||||
#rd-tab-bar::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0; right: 0; bottom: -8px;
|
|
||||||
height: 8px;
|
|
||||||
background: linear-gradient(to bottom, rgba(0,0,0,0.25), transparent);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
#rd-tab-bar.rd-scrolled::after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div id="recipe-detail-view" class="absolute inset-0 bg-[#2d2e2b] z-30 transition-all duration-300 ease-in-out translate-x-full opacity-0 pointer-events-none flex flex-col overflow-hidden" style="background:#2d2e2b !important; background-image:none !important;">
|
|
||||||
<div class="absolute top-0 w-full p-3.5 flex justify-between z-40 mt-3">
|
<div class="absolute top-0 w-full p-3.5 flex justify-between z-40 mt-3">
|
||||||
<button onclick="closeRecipeDetail()" class="w-9 h-9 rounded-full border flex items-center justify-center transition-opacity opacity-95 hover:opacity-100" style="background:rgba(47,47,45,0.92) !important; backdrop-filter:none !important; border-color:#444442 !important; color:#ddd6ca !important;">
|
<button id="rd-back-btn" onclick="closeRecipeDetail()" class="w-9 h-9 rounded-full flex items-center justify-center transition-opacity opacity-95 hover:opacity-100" style="background:rgba(57,57,55,0.93) !important; backdrop-filter:none !important; box-shadow:0 4px 9px rgba(0,0,0,0.33) !important; color:#ddd6ca !important; transition:box-shadow 180ms ease, background-color 180ms ease, opacity 180ms ease;">
|
||||||
<i class="fas fa-arrow-left text-[13px]"></i>
|
<i class="fas fa-arrow-left text-[13px]"></i>
|
||||||
</button>
|
</button>
|
||||||
<button id="rd-add-to-planner-btn" class="h-9 px-3 rounded-full border flex items-center justify-center gap-1.5 transition-opacity opacity-95 hover:opacity-100 text-[12px] font-semibold" style="background:rgba(47,47,45,0.92) !important; backdrop-filter:none !important; border-color:#444442 !important; color:#ddd6ca !important;">
|
<button id="rd-add-to-planner-btn" class="h-9 px-3 rounded-full flex items-center justify-center gap-1.5 transition-opacity opacity-95 hover:opacity-100 text-[12px] font-semibold" style="background:rgba(57,57,55,0.93) !important; backdrop-filter:none !important; box-shadow:0 3px 8px rgba(0,0,0,0.28) !important; color:#ddd6ca !important; transition:box-shadow 180ms ease, background-color 180ms ease, opacity 180ms ease;">
|
||||||
<i class="fas fa-calendar-plus text-[11px]"></i> Zaplanuj
|
<i class="fas fa-calendar-plus text-[11px]"></i> Zaplanuj
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="rd-hero" class="h-[236px] shrink-0 w-full relative overflow-hidden" style="background:linear-gradient(180deg, #3a3937 0%, #23221e 100%) !important;">
|
<div id="rd-scroll-container" class="absolute inset-0 z-10 overflow-y-auto no-scrollbar" style="overscroll-behavior-y:none;">
|
||||||
<img id="rd-hero-img" src="" alt="" class="w-full h-full object-cover hidden">
|
<div id="rd-hero" class="h-[236px] w-full relative overflow-hidden" style="background:linear-gradient(180deg, #3a3937 0%, #23221e 100%) !important; will-change:height,opacity;">
|
||||||
|
<img id="rd-hero-img" src="" alt="" class="w-full h-full object-cover hidden" style="will-change:transform;">
|
||||||
<div class="absolute inset-0 pointer-events-none" style="background:linear-gradient(to bottom, rgba(45,46,43,0.1), rgba(45,46,43,0.4), rgba(45,46,43,0.92));"></div>
|
<div class="absolute inset-0 pointer-events-none" style="background:linear-gradient(to bottom, rgba(45,46,43,0.1), rgba(45,46,43,0.4), rgba(45,46,43,0.92));"></div>
|
||||||
<span id="rd-hero-label" class="absolute inset-0 z-10 flex items-center justify-center font-medium text-[15px]" style="color:#ddd6ca;"></span>
|
<span id="rd-hero-label" class="absolute inset-0 z-10 flex items-center justify-center font-medium text-[15px]" style="color:#ddd6ca;"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-[#2d2e2b] rounded-t-3xl -mt-6 relative z-30 pt-6 flex flex-col flex-1 overflow-hidden" style="background:#2d2e2b !important; background-image:none !important; box-shadow:0 -8px 20px rgba(0,0,0,0.35) !important;">
|
<div id="rd-content-body" class="bg-[#2d2e2b] rounded-t-3xl -mt-6 relative z-30 pt-6 min-h-screen" style="background:#2d2e2b !important; background-image:none !important; box-shadow:0 -8px 20px rgba(0,0,0,0.35) !important;">
|
||||||
<div class="mb-3 px-5 shrink-0">
|
<div class="mb-3 px-5">
|
||||||
<div class="flex justify-between items-start mb-2.5">
|
<div class="flex justify-between items-start mb-2.5">
|
||||||
<h1 id="rd-title" class="text-xl font-bold leading-tight" style="color:#ddd6ca;"></h1>
|
<h1 id="rd-title" class="text-xl font-bold leading-tight" style="color:#ddd6ca;"></h1>
|
||||||
</div>
|
</div>
|
||||||
<div id="rd-tags" class="flex flex-wrap gap-1.5 mb-3"></div>
|
|
||||||
<div class="flex flex-wrap gap-2 text-[13px] font-medium">
|
|
||||||
<div class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border" style="background:#2f2f2d; border-color:#444442; color:#d7d2c8;">
|
|
||||||
<i class="fas fa-clock text-[10px]" style="color:#9b978f;"></i>
|
|
||||||
<span id="rd-time"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="rd-tab-bar" class="flex border-b mb-2 px-5 shrink-0 relative z-10" style="border-color:#444442;">
|
<div class="px-5 pt-2">
|
||||||
<button class="flex-1 pb-2.5 text-[13px] border-b-2 rd-tab-btn" data-rd-tab="ingredients" style="color:#ddd6ca; border-bottom-color:#787876; font-weight:600;">Składniki</button>
|
<h2 class="text-[11px] font-bold uppercase tracking-wider mb-3" style="color:${RD_THEME.textMuted};">Składniki</h2>
|
||||||
<button class="flex-1 pb-2.5 text-[13px] border-b-2 rd-tab-btn" data-rd-tab="steps" style="color:#9b978f; border-bottom-color:transparent; font-weight:500;">Kroki</button>
|
<div id="rd-tab-ingredients"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 overflow-y-auto px-5 pt-2 pb-10 no-scrollbar relative" style="background:#2d2e2b !important; background-image:none !important; padding-bottom:calc(2.5rem + env(safe-area-inset-bottom));">
|
<div class="px-5 pt-2" style="padding-bottom:calc(2.5rem + env(safe-area-inset-bottom));">
|
||||||
<div id="rd-tab-ingredients" class="rd-tab-content block animate-fade-in"></div>
|
<div class="border-t my-4" style="border-color:${RD_THEME.border};"></div>
|
||||||
<div id="rd-tab-steps" class="rd-tab-content hidden animate-fade-in"></div>
|
<h2 class="text-[11px] font-bold uppercase tracking-wider mb-3" style="color:${RD_THEME.textMuted};">Kroki</h2>
|
||||||
|
<div id="rd-tab-steps"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,6 +84,7 @@ let currentAddedIngredients = [];
|
|||||||
let currentProductSelections = {};
|
let currentProductSelections = {};
|
||||||
let expandedAlternatives = new Set();
|
let expandedAlternatives = new Set();
|
||||||
let ingredientCard = null;
|
let ingredientCard = null;
|
||||||
|
let resetScrollState = null;
|
||||||
|
|
||||||
function isPlannedMode() {
|
function isPlannedMode() {
|
||||||
return currentMode === 'planned';
|
return currentMode === 'planned';
|
||||||
@@ -225,31 +194,11 @@ function populateDetail(recipeId, options = {}) {
|
|||||||
heroLabel.textContent = `Zdjęcie: ${recipe.title}`;
|
heroLabel.textContent = `Zdjęcie: ${recipe.title}`;
|
||||||
}
|
}
|
||||||
document.getElementById('rd-title').textContent = recipe.title;
|
document.getElementById('rd-title').textContent = recipe.title;
|
||||||
document.getElementById('rd-time').textContent = `${recipe.minutes} min`;
|
|
||||||
document.getElementById('rd-add-to-planner-btn')?.classList.toggle('hidden', isPlannedMode());
|
document.getElementById('rd-add-to-planner-btn')?.classList.toggle('hidden', isPlannedMode());
|
||||||
|
|
||||||
const tagsHtml = [];
|
|
||||||
for (const slotId of recipe.allowedSlots) {
|
|
||||||
const label = slotLabelMap[slotId];
|
|
||||||
if (label) tagsHtml.push(renderTagChip(label));
|
|
||||||
}
|
|
||||||
for (const tag of (recipe.tags || [])) {
|
|
||||||
tagsHtml.push(renderTagChip(tag));
|
|
||||||
}
|
|
||||||
document.getElementById('rd-tags').innerHTML = tagsHtml.join('');
|
|
||||||
|
|
||||||
renderIngredients(recipe);
|
renderIngredients(recipe);
|
||||||
renderSteps(recipe);
|
renderSteps(recipe);
|
||||||
|
resetScrollState?.();
|
||||||
const tabBtns = document.querySelectorAll('.rd-tab-btn');
|
|
||||||
const tabs = document.querySelectorAll('.rd-tab-content');
|
|
||||||
tabBtns.forEach((b) => {
|
|
||||||
setTabButtonState(b, false);
|
|
||||||
});
|
|
||||||
setTabButtonState(tabBtns[0], true);
|
|
||||||
tabs.forEach((t) => { t.classList.add('hidden'); t.classList.remove('block'); });
|
|
||||||
document.getElementById('rd-tab-ingredients')?.classList.remove('hidden');
|
|
||||||
document.getElementById('rd-tab-ingredients')?.classList.add('block');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── helpers ───────────────────────────────────────────── */
|
/* ── helpers ───────────────────────────────────────────── */
|
||||||
@@ -523,9 +472,9 @@ function renderSteps(recipe) {
|
|||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="space-y-0.5 pb-5">
|
<div class="space-y-0.5 pb-5">
|
||||||
${steps.map((step, i) => `
|
${steps.map((step, i) => `
|
||||||
<div class="rounded-xl px-3 py-2 flex gap-3" style="background:transparent !important; background-image:none !important; box-shadow:none !important; border:none !important;">
|
<div class="relative rounded-xl py-2 pl-3 pr-3" style="background:transparent !important; background-image:none !important; box-shadow:none !important; border:none !important;">
|
||||||
<div class="w-6 h-6 rounded-full flex items-center justify-center text-[11px] font-bold shrink-0" style="background:transparent !important; border:none !important; box-shadow:none !important; color:${RD_THEME.textSecondary} !important;">${i + 1}</div>
|
<div class="absolute top-2 left-0 w-3 h-6 flex items-center justify-start text-[11px] font-bold shrink-0 tabular-nums" style="background:transparent !important; border:none !important; box-shadow:none !important; color:${RD_THEME.textSecondary} !important;">${i + 1}.</div>
|
||||||
<div class="pt-0.5"><p class="text-[13px] leading-relaxed" style="color:${RD_THEME.textSecondary};">${escapeHtml(step)}</p></div>
|
<p class="pl-4 text-[13px] leading-relaxed" style="color:${RD_THEME.textSecondary};">${escapeHtml(step)}</p>
|
||||||
</div>`).join('')}
|
</div>`).join('')}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -536,36 +485,80 @@ export function setupRecipeDetail() {
|
|||||||
ingredientCard = createIngredientCardController({ idBase: 'rd-ing-card', defaultSourceNote: 'Z planera' });
|
ingredientCard = createIngredientCardController({ idBase: 'rd-ing-card', defaultSourceNote: 'Z planera' });
|
||||||
ingredientCard.bind();
|
ingredientCard.bind();
|
||||||
|
|
||||||
document.querySelectorAll('.rd-tab-btn').forEach((btn) => {
|
/* ── collapsing hero on scroll ─────────────── */
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
const tabId = btn.dataset.rdTab;
|
const HERO_MAX = 236;
|
||||||
document.querySelectorAll('.rd-tab-content').forEach((el) => {
|
const scrollContainer = document.getElementById('rd-scroll-container');
|
||||||
el.classList.add('hidden');
|
const hero = document.getElementById('rd-hero');
|
||||||
el.classList.remove('block');
|
const heroImg = document.getElementById('rd-hero-img');
|
||||||
|
const topActionButtons = [
|
||||||
|
document.getElementById('rd-back-btn'),
|
||||||
|
document.getElementById('rd-add-to-planner-btn'),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
function syncTopActionButtons(progress) {
|
||||||
|
const shadowY = 3 + (progress * 3);
|
||||||
|
const shadowBlur = 8 + (progress * 6);
|
||||||
|
const shadowAlpha = 0.28 + (progress * 0.16);
|
||||||
|
const backgroundAlpha = 0.93 + (progress * 0.05);
|
||||||
|
topActionButtons.forEach((button) => {
|
||||||
|
const isRoundButton = button.id === 'rd-back-btn';
|
||||||
|
const effectiveShadowY = isRoundButton ? shadowY + 1 : shadowY;
|
||||||
|
const effectiveShadowBlur = isRoundButton ? shadowBlur + 1 : shadowBlur;
|
||||||
|
const effectiveShadowAlpha = isRoundButton ? shadowAlpha + 0.05 : shadowAlpha;
|
||||||
|
button.style.boxShadow = `0 ${effectiveShadowY}px ${effectiveShadowBlur}px rgba(0,0,0,${effectiveShadowAlpha})`;
|
||||||
|
button.style.background = `rgba(57,57,55,${backgroundAlpha})`;
|
||||||
});
|
});
|
||||||
const target = document.getElementById(`rd-tab-${tabId}`);
|
|
||||||
if (target) {
|
|
||||||
target.classList.remove('hidden');
|
|
||||||
target.classList.add('block');
|
|
||||||
target.parentElement.scrollTop = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.rd-tab-btn').forEach((b) => {
|
if (scrollContainer && hero) {
|
||||||
setTabButtonState(b, false);
|
let ticking = false;
|
||||||
});
|
let lastTouchY = null;
|
||||||
setTabButtonState(btn, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ── tab-bar scroll shadow ─────────────────── */
|
|
||||||
|
|
||||||
const scrollContainer = document.querySelector('#recipe-detail-view .overflow-y-auto');
|
|
||||||
const tabBar = document.getElementById('rd-tab-bar');
|
|
||||||
if (scrollContainer && tabBar) {
|
|
||||||
scrollContainer.addEventListener('scroll', () => {
|
scrollContainer.addEventListener('scroll', () => {
|
||||||
tabBar.classList.toggle('rd-scrolled', scrollContainer.scrollTop > 2);
|
if (ticking) return;
|
||||||
});
|
ticking = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const scrollY = Math.max(0, scrollContainer.scrollTop);
|
||||||
|
const collapse = Math.min(scrollY, HERO_MAX);
|
||||||
|
const progress = collapse / HERO_MAX;
|
||||||
|
hero.style.height = `${HERO_MAX - collapse}px`;
|
||||||
|
hero.style.opacity = String(Math.max(0, 1 - collapse / HERO_MAX));
|
||||||
|
syncTopActionButtons(progress);
|
||||||
|
if (heroImg) {
|
||||||
|
heroImg.style.transform = `translateY(${collapse * 0.4}px) scale(${1 + collapse * 0.001})`;
|
||||||
}
|
}
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
scrollContainer.addEventListener('touchstart', (e) => {
|
||||||
|
lastTouchY = e.touches[0]?.clientY ?? null;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
scrollContainer.addEventListener('touchmove', (e) => {
|
||||||
|
const touchY = e.touches[0]?.clientY;
|
||||||
|
if (touchY == null) return;
|
||||||
|
if (scrollContainer.scrollTop <= 0 && lastTouchY != null && touchY > lastTouchY) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
lastTouchY = touchY;
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
scrollContainer.addEventListener('touchend', () => {
|
||||||
|
lastTouchY = null;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
scrollContainer.addEventListener('touchcancel', () => {
|
||||||
|
lastTouchY = null;
|
||||||
|
}, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
resetScrollState = () => {
|
||||||
|
if (scrollContainer) scrollContainer.scrollTop = 0;
|
||||||
|
if (hero) { hero.style.height = `${HERO_MAX}px`; hero.style.opacity = '1'; }
|
||||||
|
syncTopActionButtons(0);
|
||||||
|
if (heroImg) heroImg.style.transform = '';
|
||||||
|
};
|
||||||
|
|
||||||
/* ── planner — delegate to MealPlanEditor ─────── */
|
/* ── planner — delegate to MealPlanEditor ─────── */
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user