Extract colors
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m10s

This commit is contained in:
2026-04-18 11:12:05 +02:00
parent 59340e8afd
commit 5c21fb1e64
14 changed files with 485 additions and 430 deletions

View File

@@ -65,11 +65,11 @@ function renderAppBootError(message) {
if (!appContainer) return;
appContainer.innerHTML = `
<div class="flex h-full items-center justify-center px-6 text-center" style="background:#2d2e2b !important;">
<div class="max-w-[18rem] rounded-[1.5rem] border px-5 py-6" style="background:#2f2f2d !important; border-color:#444442 !important;">
<p class="text-sm font-semibold" style="color:#f2efe8 !important;">Nie udało się uruchomić aplikacji</p>
<p class="mt-2 text-xs leading-relaxed" style="color:#b7ada1 !important;">${message}</p>
<button type="button" onclick="window.location.reload()" class="mt-4 h-10 px-4 rounded-full border text-[12px] font-semibold" style="background:#23221e !important; border-color:#787876 !important; color:#f2efe8 !important;">Odśwież aplikację</button>
<div class="flex h-full items-center justify-center px-6 text-center" style="background:rgb(var(--app-bg-rgb)) !important;">
<div class="max-w-[18rem] rounded-[1.5rem] border px-5 py-6" style="background:rgb(var(--card-soft-rgb)) !important; border-color:rgb(var(--card-strong-rgb)) !important;">
<p class="text-sm font-semibold" style="color:rgb(var(--text-emphasis-rgb)) !important;">Nie udało się uruchomić aplikacji</p>
<p class="mt-2 text-xs leading-relaxed" style="color:rgb(var(--text-muted-rgb)) !important;">${message}</p>
<button type="button" onclick="window.location.reload()" class="mt-4 h-10 px-4 rounded-full border text-[12px] font-semibold" style="background:rgb(var(--sunken-rgb)) !important; border-color:rgb(var(--border-input-rgb)) !important; color:rgb(var(--text-emphasis-rgb)) !important;">Odśwież aplikację</button>
</div>
</div>
`;

View File

@@ -18,7 +18,11 @@ function setupThemeToggle() {
syncThemeToggleButton(btn, isDark);
const meta = document.querySelector('meta[name="theme-color"]');
if (meta) meta.setAttribute('content', isDark ? '#161513' : '#f3efe9');
if (meta) {
const varName = isDark ? '--sunken-deep-rgb' : '--app-bg-rgb';
const rgb = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
if (rgb) meta.setAttribute('content', `rgb(${rgb})`);
}
});
}

View File

@@ -79,11 +79,11 @@ function mediaHtml(image, icon, sizeClass = 'w-9 h-9', radiusClass = 'rounded-lg
const fit = image.endsWith('.svg') ? 'object-contain' : 'object-cover';
return `<img src="${esc(image)}" alt="" class="${sizeClass} ${radiusClass} ${fit} shrink-0">`;
}
return `<div class="${sizeClass} ${radiusClass} flex items-center justify-center shrink-0" style="background:#2f2f2d;"><i class="fas ${icon} text-sm" style="color:#8f8b84;"></i></div>`;
return `<div class="${sizeClass} ${radiusClass} flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${icon} text-sm" style="color:rgb(var(--text-faint-rgb));"></i></div>`;
}
function compactMetaText(text, tone = 'default') {
const color = tone === 'success' ? '#6ee7b7' : tone === 'muted' ? '#9b978f' : '#d7d2c8';
const color = tone === 'success' ? 'rgb(var(--success-rgb))' : tone === 'muted' ? 'rgb(var(--text-dim-rgb))' : 'rgb(var(--text-body-soft-rgb))';
return `<span class="text-[10px] font-medium" style="color:${color};">${esc(text)}</span>`;
}
@@ -154,7 +154,7 @@ export function getIngredientCardHTML({
overlayClass = 'fixed inset-0 z-[70] hidden opacity-0 transition-opacity duration-200 flex items-center justify-center p-5',
overlayStyle = 'pointer-events:none; background:rgba(0,0,0,0.5);',
cardClass = 'relative w-full max-w-xs rounded-2xl shadow-2xl overflow-hidden',
cardStyle = 'background:#2d2e2b; pointer-events:auto; max-height:85vh; overflow-y:auto; transform:translateY(0.75rem); opacity:0; transition:transform 220ms ease, opacity 220ms ease;',
cardStyle = 'background:rgb(var(--app-bg-rgb)); pointer-events:auto; max-height:85vh; overflow-y:auto; transform:translateY(0.75rem); opacity:0; transition:transform 220ms ease, opacity 220ms ease;',
} = {}) {
if (!idBase) throw new Error('getIngredientCardHTML requires idBase');
return `
@@ -162,24 +162,24 @@ export function getIngredientCardHTML({
<div id="${idBase}" class="${cardClass}" style="${cardStyle}">
<div class="relative px-4 pt-4 pb-2">
<div class="flex items-start gap-3 pr-10">
<div id="${idBase}-hero" class="relative w-20 h-20 rounded-2xl flex items-center justify-center shrink-0 overflow-hidden" style="background:#393937;">
<div id="${idBase}-hero" class="relative w-20 h-20 rounded-2xl flex items-center justify-center shrink-0 overflow-hidden" style="background:rgb(var(--card-rgb));">
<img id="${idBase}-img" class="w-full h-full hidden" alt="" />
<div id="${idBase}-fallback" class="absolute inset-0 flex items-center justify-center">
<i id="${idBase}-fallback-icon" class="fas fa-box-open text-2xl" style="color:#6d6c67;"></i>
<i id="${idBase}-fallback-icon" class="fas fa-box-open text-2xl" style="color:rgb(var(--text-subdued-rgb));"></i>
</div>
</div>
<div class="flex-1 min-w-0 pt-0.5">
<div class="flex items-center gap-1.5">
<button type="button" id="${idBase}-back" class="hidden w-5 h-5 rounded-full items-center justify-center shrink-0" style="background:#393937; color:#ddd6ca;" aria-label="Wróć do składnika">
<button type="button" id="${idBase}-back" class="hidden w-5 h-5 rounded-full items-center justify-center shrink-0" style="background:rgb(var(--card-rgb)); color:rgb(var(--text-body-rgb));" aria-label="Wróć do składnika">
<i class="fas fa-chevron-left text-[9px]"></i>
</button>
<p id="${idBase}-category" class="text-[10px] font-semibold uppercase tracking-wider truncate" style="color:#6ee7b7;"></p>
<p id="${idBase}-category" class="text-[10px] font-semibold uppercase tracking-wider truncate" style="color:rgb(var(--success-rgb));"></p>
</div>
<h3 id="${idBase}-name" class="text-[15px] font-bold leading-snug mt-0.5" style="color:#ddd6ca;"></h3>
<p id="${idBase}-subtitle" class="text-[11px] mt-0.5 hidden" style="color:#9b978f;"></p>
<h3 id="${idBase}-name" class="text-[15px] font-bold leading-snug mt-0.5" style="color:rgb(var(--text-body-rgb));"></h3>
<p id="${idBase}-subtitle" class="text-[11px] mt-0.5 hidden" style="color:rgb(var(--text-dim-rgb));"></p>
</div>
</div>
<button type="button" id="${idBase}-close" class="absolute top-3 right-3 w-8 h-8 rounded-full flex items-center justify-center" style="background:#393937; color:#ddd6ca;" aria-label="Zamknij">
<button type="button" id="${idBase}-close" class="absolute top-3 right-3 w-8 h-8 rounded-full flex items-center justify-center" style="background:rgb(var(--card-rgb)); color:rgb(var(--text-body-rgb));" aria-label="Zamknij">
<i class="fas fa-times text-sm"></i>
</button>
</div>
@@ -319,24 +319,24 @@ export function createIngredientCardController({ idBase, defaultSourceNote = 'Ze
const nutritionMeta = hint ? `${unitScope}${hint}` : unitScope;
wrap.innerHTML = `
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:#9b978f;">Wartości odżywcze</p>
<p class="text-[10px] mb-1.5" style="color:#9b978f;">${esc(nutritionMeta)}</p>
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Wartości odżywcze</p>
<p class="text-[10px] mb-1.5" style="color:rgb(var(--text-dim-rgb));">${esc(nutritionMeta)}</p>
<div class="grid grid-cols-4 gap-1.5">
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
<p class="text-[15px] font-bold tabular-nums leading-tight" style="color:#ddd6ca;">${nutrition.kcal}</p>
<p class="text-[9px] font-medium" style="color:#9b978f;">kcal</p>
<div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold tabular-nums leading-tight" style="color:rgb(var(--text-body-rgb));">${nutrition.kcal}</p>
<p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">kcal</p>
</div>
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-blue-400 tabular-nums leading-tight">${formatQty(nutrition.protein)}g</p>
<p class="text-[9px] font-medium" style="color:#9b978f;">białko</p>
<p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">białko</p>
</div>
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-amber-400 tabular-nums leading-tight">${formatQty(nutrition.fat)}g</p>
<p class="text-[9px] font-medium" style="color:#9b978f;">tłuszcz</p>
<p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">tłuszcz</p>
</div>
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-orange-400 tabular-nums leading-tight">${formatQty(nutrition.carbs)}g</p>
<p class="text-[9px] font-medium" style="color:#9b978f;">węgl.</p>
<p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">węgl.</p>
</div>
</div>`;
}
@@ -364,35 +364,35 @@ export function createIngredientCardController({ idBase, defaultSourceNote = 'Ze
const actionLabel = state.stockEditorOpen ? 'Anuluj' : 'Zmień';
wrap.innerHTML = `
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:#9b978f;">Zapas</p>
<div class="rounded-2xl border px-3 py-3" style="background:#393937; border-color:#444442;">
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Zapas</p>
<div class="rounded-2xl border px-3 py-3" style="background:rgb(var(--card-rgb)); border-color:rgb(var(--card-strong-rgb));">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<p class="text-[16px] font-bold tabular-nums" style="color:#6ee7b7;">${esc(stockValueLabel)}</p>
${stockSubLabel ? `<p class="text-[11px] mt-1" style="color:#9b978f;">${esc(stockSubLabel)}</p>` : ''}
<p class="text-[16px] font-bold tabular-nums" style="color:rgb(var(--success-rgb));">${esc(stockValueLabel)}</p>
${stockSubLabel ? `<p class="text-[11px] mt-1" style="color:rgb(var(--text-dim-rgb));">${esc(stockSubLabel)}</p>` : ''}
</div>
<button type="button" class="ingredient-card-stock-toggle inline-flex items-center rounded-full px-2.5 py-1 text-[10px] font-semibold shrink-0" style="background:${state.stockEditorOpen ? '#23221e' : '#2f2f2d'}; color:${state.stockEditorOpen ? '#f2efe8' : '#d7d2c8'};">
<button type="button" class="ingredient-card-stock-toggle inline-flex items-center rounded-full px-2.5 py-1 text-[10px] font-semibold shrink-0" style="background:${state.stockEditorOpen ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-soft-rgb))'}; color:${state.stockEditorOpen ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-soft-rgb))'};">
${esc(actionLabel)}
</button>
</div>
${state.stockEditorOpen ? `
<div class="mt-3 pt-3 border-t" style="border-color:#444442;">
<div class="mt-3 pt-3 border-t" style="border-color:rgb(var(--card-strong-rgb));">
<div class="flex items-center gap-2">
<button type="button" class="ingredient-card-stock-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:#2f2f2d; color:#d7d2c8;" data-dir="-1" aria-label="Zmniejsz szkic zapasu">
<button type="button" class="ingredient-card-stock-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="-1" aria-label="Zmniejsz szkic zapasu">
<i class="fas fa-minus text-xs"></i>
</button>
<label class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:#2f2f2d;">
<input type="number" min="0" step="${usesPackStep ? '1' : step}" value="${draftInputValue}" class="ingredient-card-stock-input w-20 bg-transparent text-center text-[14px] font-semibold tabular-nums outline-none appearance-none" style="color:#ddd6ca; background:transparent !important; border:none !important; box-shadow:none !important; -webkit-appearance:none; -moz-appearance:textfield;">
<span class="text-[12px] font-medium shrink-0" style="color:#9b978f;">${esc(draftInputUnit)}</span>
<label class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:rgb(var(--card-soft-rgb));">
<input type="number" min="0" step="${usesPackStep ? '1' : step}" value="${draftInputValue}" class="ingredient-card-stock-input w-20 bg-transparent text-center text-[14px] font-semibold tabular-nums outline-none appearance-none" style="color:rgb(var(--text-body-rgb)); background:transparent !important; border:none !important; box-shadow:none !important; -webkit-appearance:none; -moz-appearance:textfield;">
<span class="text-[12px] font-medium shrink-0" style="color:rgb(var(--text-dim-rgb));">${esc(draftInputUnit)}</span>
</label>
<button type="button" class="ingredient-card-stock-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:#2f2f2d; color:#d7d2c8;" data-dir="1" aria-label="Zwiększ szkic zapasu">
<button type="button" class="ingredient-card-stock-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="1" aria-label="Zwiększ szkic zapasu">
<i class="fas fa-plus text-xs"></i>
</button>
</div>
${usesPackStep ? `<p class="text-[10px] mt-2 text-right" style="color:#9b978f;">${esc(formatQtyWithUnit(draftQty, def.pantryUnit))}</p>` : ''}
${usesPackStep ? `<p class="text-[10px] mt-2 text-right" style="color:rgb(var(--text-dim-rgb));">${esc(formatQtyWithUnit(draftQty, def.pantryUnit))}</p>` : ''}
<div class="flex items-center justify-between gap-3 mt-3">
<button type="button" class="ingredient-card-stock-clear text-[11px] font-semibold" style="color:#9b978f;">Wyzeruj</button>
<button type="button" class="ingredient-card-stock-save inline-flex items-center rounded-full px-3 py-1.5 text-[11px] font-semibold" style="background:#ddd6ca; color:#2d2e2b;">Zapisz</button>
<button type="button" class="ingredient-card-stock-clear text-[11px] font-semibold" style="color:rgb(var(--text-dim-rgb));">Wyzeruj</button>
<button type="button" class="ingredient-card-stock-save inline-flex items-center rounded-full px-3 py-1.5 text-[11px] font-semibold" style="background:rgb(var(--text-body-rgb)); color:rgb(var(--app-bg-rgb));">Zapisz</button>
</div>
</div>` : ''}
</div>`;
@@ -473,37 +473,37 @@ export function createIngredientCardController({ idBase, defaultSourceNote = 'Ze
const actionLabel = state.shopEditorOpen ? 'Anuluj' : (hasShoppingItem ? 'Zmień' : 'Dodaj');
wrap.innerHTML = `
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:#9b978f;">Lista zakupów</p>
<div class="rounded-2xl border px-3 py-3" style="background:#393937; border-color:#444442;">
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Lista zakupów</p>
<div class="rounded-2xl border px-3 py-3" style="background:rgb(var(--card-rgb)); border-color:rgb(var(--card-strong-rgb));">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<p class="text-[16px] font-bold tabular-nums" style="color:${hasShoppingItem ? '#ddd6ca' : '#9b978f'};">${esc(shopValueLabel)}</p>
${shopSubLabel ? `<p class="text-[11px] mt-1" style="color:#9b978f;">${esc(shopSubLabel)}</p>` : ''}
<p class="text-[16px] font-bold tabular-nums" style="color:${hasShoppingItem ? 'rgb(var(--text-body-rgb))' : 'rgb(var(--text-dim-rgb))'};">${esc(shopValueLabel)}</p>
${shopSubLabel ? `<p class="text-[11px] mt-1" style="color:rgb(var(--text-dim-rgb));">${esc(shopSubLabel)}</p>` : ''}
</div>
<button type="button" class="ingredient-card-shop-toggle inline-flex items-center rounded-full px-2.5 py-1 text-[10px] font-semibold shrink-0" style="background:${state.shopEditorOpen ? '#23221e' : '#2f2f2d'}; color:${state.shopEditorOpen ? '#f2efe8' : '#d7d2c8'};">
<button type="button" class="ingredient-card-shop-toggle inline-flex items-center rounded-full px-2.5 py-1 text-[10px] font-semibold shrink-0" style="background:${state.shopEditorOpen ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-soft-rgb))'}; color:${state.shopEditorOpen ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-soft-rgb))'};">
${esc(actionLabel)}
</button>
</div>
${state.shopEditorOpen ? `
<div class="mt-3 pt-3 border-t" style="border-color:#444442;">
<div class="mt-3 pt-3 border-t" style="border-color:rgb(var(--card-strong-rgb));">
<div class="flex items-center gap-2">
<button type="button" class="ingredient-card-shop-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:#2f2f2d; color:#d7d2c8;" data-dir="-1" aria-label="Zmniejsz ilość na liście">
<button type="button" class="ingredient-card-shop-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="-1" aria-label="Zmniejsz ilość na liście">
<i class="fas fa-minus text-xs"></i>
</button>
<label class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:#2f2f2d;">
<input type="number" min="0" step="${usesPackStep ? '1' : defaultAmount}" value="${shopInputValue}" class="ingredient-card-shop-input w-20 bg-transparent text-center text-[14px] font-semibold tabular-nums outline-none appearance-none" style="color:#ddd6ca; background:transparent !important; border:none !important; box-shadow:none !important; -webkit-appearance:none; -moz-appearance:textfield;">
<span class="text-[12px] font-medium shrink-0" style="color:#9b978f;">${esc(shopInputUnit)}</span>
<label class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:rgb(var(--card-soft-rgb));">
<input type="number" min="0" step="${usesPackStep ? '1' : defaultAmount}" value="${shopInputValue}" class="ingredient-card-shop-input w-20 bg-transparent text-center text-[14px] font-semibold tabular-nums outline-none appearance-none" style="color:rgb(var(--text-body-rgb)); background:transparent !important; border:none !important; box-shadow:none !important; -webkit-appearance:none; -moz-appearance:textfield;">
<span class="text-[12px] font-medium shrink-0" style="color:rgb(var(--text-dim-rgb));">${esc(shopInputUnit)}</span>
</label>
<button type="button" class="ingredient-card-shop-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:#2f2f2d; color:#d7d2c8;" data-dir="1" aria-label="Zwiększ ilość na liście">
<button type="button" class="ingredient-card-shop-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="1" aria-label="Zwiększ ilość na liście">
<i class="fas fa-plus text-xs"></i>
</button>
</div>
${usesPackStep ? `<p class="text-[10px] mt-2 text-right" style="color:#9b978f;">${esc(formatQtyWithUnit(draftQty, def.pantryUnit))}</p>` : ''}
${usesPackStep ? `<p class="text-[10px] mt-2 text-right" style="color:rgb(var(--text-dim-rgb));">${esc(formatQtyWithUnit(draftQty, def.pantryUnit))}</p>` : ''}
<div class="flex items-center justify-between gap-3 mt-3">
${hasShoppingItem
? '<button type="button" class="ingredient-card-shop-remove text-[11px] font-semibold" style="color:#9b978f;">Usuń z listy</button>'
? '<button type="button" class="ingredient-card-shop-remove text-[11px] font-semibold" style="color:rgb(var(--text-dim-rgb));">Usuń z listy</button>'
: '<span></span>'}
<button type="button" class="ingredient-card-shop-save inline-flex items-center rounded-full px-3 py-1.5 text-[11px] font-semibold" style="background:#ddd6ca; color:#2d2e2b;">Zapisz</button>
<button type="button" class="ingredient-card-shop-save inline-flex items-center rounded-full px-3 py-1.5 text-[11px] font-semibold" style="background:rgb(var(--text-body-rgb)); color:rgb(var(--app-bg-rgb));">Zapisz</button>
</div>
</div>` : ''}
</div>`;
@@ -611,24 +611,24 @@ export function createIngredientCardController({ idBase, defaultSourceNote = 'Ze
const shoppingAmount = shoppingItem?.amount || 0;
const pantryLabel = pantryQty > 0 ? `${formatQty(pantryQty)} ${unit}` : '—';
const pantryColor = pantryQty > 0 ? '#ddd6ca' : '#6d6c67';
const pantryColor = pantryQty > 0 ? 'rgb(var(--text-body-rgb))' : 'rgb(var(--text-subdued-rgb))';
const shoppingLabel = shoppingAmount > 0 ? `${formatQty(shoppingAmount)} ${unit}` : '';
return `<button type="button" class="ingredient-card-product-row w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-left transition-colors active:scale-[0.99]" style="background:#393937; border:1px solid transparent;" data-product-id="${esc(productId)}">
return `<button type="button" class="ingredient-card-product-row w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-left transition-colors active:scale-[0.99]" style="background:rgb(var(--card-rgb)); border:1px solid transparent;" data-product-id="${esc(productId)}">
${mediaHtml(product.image || def.image, icon)}
<div class="flex-1 min-w-0">
<span class="text-[13px] font-semibold truncate block" style="color:#ddd6ca;">${esc(product.name)}</span>
${product.packLabel ? `<span class="text-[10px] block mt-0.5" style="color:#9b978f;">${esc(product.packLabel)}</span>` : ''}
<span class="text-[13px] font-semibold truncate block" style="color:rgb(var(--text-body-rgb));">${esc(product.name)}</span>
${product.packLabel ? `<span class="text-[10px] block mt-0.5" style="color:rgb(var(--text-dim-rgb));">${esc(product.packLabel)}</span>` : ''}
</div>
<div class="flex flex-col items-end gap-0.5 shrink-0 tabular-nums">
<span class="text-[11px] font-semibold inline-flex items-center gap-1" style="color:${pantryColor};">
<i class="fas fa-box text-[9px]" style="color:#8f8b84;"></i>${esc(pantryLabel)}
<i class="fas fa-box text-[9px]" style="color:rgb(var(--text-faint-rgb));"></i>${esc(pantryLabel)}
</span>
${shoppingLabel ? `<span class="text-[11px] font-semibold inline-flex items-center gap-1" style="color:#ddd6ca;">
<i class="fas fa-cart-shopping text-[9px]" style="color:#8f8b84;"></i>${esc(shoppingLabel)}
${shoppingLabel ? `<span class="text-[11px] font-semibold inline-flex items-center gap-1" style="color:rgb(var(--text-body-rgb));">
<i class="fas fa-cart-shopping text-[9px]" style="color:rgb(var(--text-faint-rgb));"></i>${esc(shoppingLabel)}
</span>` : ''}
</div>
<i class="fas fa-chevron-right text-[10px] shrink-0" style="color:#8f8b84;"></i>
<i class="fas fa-chevron-right text-[10px] shrink-0" style="color:rgb(var(--text-faint-rgb));"></i>
</button>`;
}
@@ -649,7 +649,7 @@ export function createIngredientCardController({ idBase, defaultSourceNote = 'Ze
const kitchenItems = (kitchen && kitchen.type === 'kitchen') ? kitchen.items : [];
wrap.innerHTML = `
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:#9b978f;">Produkty</p>
<p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Produkty</p>
<div class="space-y-1.5">
${products.map((product) => productRowHtml(state.ingredientId, product.id, pantry, kitchenItems)).join('')}
</div>`;

View File

@@ -19,7 +19,7 @@ export const CALENDAR_MONTHS_SHORT = [
export const CALENDAR_WEEKDAYS_SHORT = ['pn', 'wt', 'śr', 'cz', 'pt', 'so', 'nd'];
export const CALENDAR_DAY_ATTR = 'data-calendar-day';
export const CALENDAR_HANDLE_CLASS = 'block h-1 w-10 rounded-full bg-[#6d6c67]/75';
export const CALENDAR_HANDLE_CLASS = 'block h-1 w-10 rounded-full bg-[rgb(var(--text-subdued-rgb))]/75';
function getCalendarDayHTML(day, meta, dayState, dayAttr, theme = {}) {
const { mode, selectedDate } = meta;
@@ -27,9 +27,9 @@ function getCalendarDayHTML(day, meta, dayState, dayAttr, theme = {}) {
const showIndicator = !!dayState.showIndicator;
const isDisabled = !!dayState.disabled;
const isDimmed = !!dayState.dimmed && !isSelected;
const defaultBg = '#2f2f2d';
const defaultBorder = '#444442';
const defaultText = '#d7d2c8';
const defaultBg = 'rgb(var(--card-soft-rgb))';
const defaultBorder = 'rgb(var(--card-strong-rgb))';
const defaultText = 'rgb(var(--text-body-soft-rgb))';
let bg;
let borderColor;
@@ -37,12 +37,12 @@ function getCalendarDayHTML(day, meta, dayState, dayAttr, theme = {}) {
let borderClass = 'border';
if (isSelected) {
bg = theme.selectedBg || '#23221e';
borderColor = theme.selectedBorder || '#787876';
text = theme.selectedText || '#f2efe8';
bg = theme.selectedBg || 'rgb(var(--sunken-rgb))';
borderColor = theme.selectedBorder || 'rgb(var(--border-input-rgb))';
text = theme.selectedText || 'rgb(var(--text-emphasis-rgb))';
} else if (isDimmed) {
bg = theme.dimmedBg ?? theme.bg ?? defaultBg;
text = theme.dimText || '#7d7a74';
text = theme.dimText || 'rgb(var(--text-faint-rgb))';
borderClass = 'border-0';
} else {
bg = theme.bg || defaultBg;
@@ -50,7 +50,7 @@ function getCalendarDayHTML(day, meta, dayState, dayAttr, theme = {}) {
text = theme.text || defaultText;
}
const dot = isSelected ? (theme.selectedDot || '#f2efe8') : (theme.dot || '#a59f92');
const dot = isSelected ? (theme.selectedDot || 'rgb(var(--text-emphasis-rgb))') : (theme.dot || 'rgb(var(--text-faint-rgb))');
const opacity = isDimmed ? String(theme.dimOpacity ?? 0.72) : '1';
const borderStyle = isDimmed ? 'border:none;' : `border-color:${borderColor};`;
const outerClass = `${mode === 'month' ? 'mx-auto ' : ''}flex h-[2.05rem] w-full min-w-0 max-w-full items-center justify-center rounded-full ${borderClass} text-xs font-medium transition-colors leading-tight overflow-hidden`;
@@ -118,10 +118,10 @@ export function createCalendarTopbarHTML({
todayId,
nextId,
wrapperClass = 'px-4 pt-4 pb-3 flex items-center justify-end',
controlsStyle = 'background:#2f2f2d;border-color:#444442;',
navButtonClass = 'shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[#d7d2c8] transition-colors',
todayButtonActiveClass = 'h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center rounded-full bg-transparent px-1.5 text-[10px] font-semibold leading-none tabular-nums text-[#d7d2c8] active:bg-transparent whitespace-nowrap',
todayButtonDimClass = 'h-full shrink-0 inline-flex items-center justify-center rounded-full px-2 text-[10px] font-semibold leading-none text-[#7d7a74] cursor-default',
controlsStyle = 'background:rgb(var(--card-soft-rgb));border-color:rgb(var(--card-strong-rgb));',
navButtonClass = 'shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[rgb(var(--text-body-soft-rgb))] transition-colors',
todayButtonActiveClass = 'h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center rounded-full bg-transparent px-1.5 text-[10px] font-semibold leading-none tabular-nums text-[rgb(var(--text-body-soft-rgb))] active:bg-transparent whitespace-nowrap',
todayButtonDimClass = 'h-full shrink-0 inline-flex items-center justify-center rounded-full px-2 text-[10px] font-semibold leading-none text-[rgb(var(--text-faint-rgb))] cursor-default',
}) {
return `
<div class="${wrapperClass}">
@@ -168,7 +168,7 @@ export function syncCalendarTodayButton(buttonEl, isOnToday, selectedDate, optio
ariaLabelCurrent = 'Widok jest ustawiony na bieżący okres',
} = options;
const active = buttonEl.dataset.calActiveClass
|| 'h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center rounded-full bg-transparent px-1.5 text-[10px] font-semibold leading-none tabular-nums text-[#d7d2c8] active:bg-transparent whitespace-nowrap';
|| 'h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center rounded-full bg-transparent px-1.5 text-[10px] font-semibold leading-none tabular-nums text-[rgb(var(--text-body-soft-rgb))] active:bg-transparent whitespace-nowrap';
if (selectedDate != null) {
buttonEl.textContent = formatCalendarSelectedDate(selectedDate);
}

View File

@@ -37,21 +37,21 @@ const slotLabel = Object.fromEntries(MEAL_SLOTS.map((s) => [s.id, s.label]));
export function getMealPlanEditorHTML() {
return `
<div id="mpe-overlay" class="absolute inset-0 z-[55] bg-black/45 hidden flex items-end" style="pointer-events:none">
<div id="mpe-sheet" class="w-full bg-[#2d2e2b] rounded-t-3xl shadow-lg flex flex-col overflow-hidden" style="pointer-events:auto; background:#2d2e2b !important; background-image:none !important; backdrop-filter:none !important; height:100dvh; max-height:100dvh; transform:translateY(100%); transition:transform 300ms cubic-bezier(0.32,0.72,0,1)">
<div class="shrink-0 px-5 pt-3 pb-2.5 border-b border-gray-100 bg-[#2d2e2b]" style="background:#2d2e2b !important; background-image:none !important;">
<div id="mpe-sheet" class="w-full bg-[rgb(var(--app-bg-rgb))] rounded-t-3xl shadow-lg flex flex-col overflow-hidden" style="pointer-events:auto; background:rgb(var(--app-bg-rgb)) !important; background-image:none !important; backdrop-filter:none !important; height:100dvh; max-height:100dvh; transform:translateY(100%); transition:transform 300ms cubic-bezier(0.32,0.72,0,1)">
<div class="shrink-0 px-5 pt-3 pb-2.5 border-b border-gray-100 bg-[rgb(var(--app-bg-rgb))]" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important;">
<div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-3"></div>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<h2 id="mpe-title" class="text-[15px] font-bold text-gray-900 leading-tight"></h2>
<p id="mpe-subtitle" class="text-[11px] text-gray-500 mt-0.5 truncate"></p>
</div>
<button id="mpe-confirm-btn" type="button" aria-label="Dodaj do planu" class="shrink-0 mt-0.5 border h-8 px-3 rounded-full font-semibold text-[12px] transition-colors inline-flex items-center justify-center gap-1.5" style="background:#dcd6cb !important; color:#2d2e2b !important; background-image:none !important; border-color:#dcd6cb !important; box-shadow:0 2px 10px rgba(0,0,0,0.18);">
<button id="mpe-confirm-btn" type="button" aria-label="Dodaj do planu" class="shrink-0 mt-0.5 border h-8 px-3 rounded-full font-semibold text-[12px] transition-colors inline-flex items-center justify-center gap-1.5" style="background:rgb(var(--text-body-rgb)) !important; color:rgb(var(--app-bg-rgb)) !important; background-image:none !important; border-color:rgb(var(--text-body-rgb)) !important; box-shadow:0 2px 10px rgba(0,0,0,0.18);">
<i id="mpe-confirm-icon" class="fas fa-plus text-[10px]" aria-hidden="true"></i>
<span id="mpe-confirm-label">Dodaj</span>
</button>
</div>
</div>
<div id="mpe-cal-wrap" class="hidden relative z-[1] shrink-0 px-5 pt-3 pb-3 bg-[#2d2e2b]" style="background:#2d2e2b !important; background-image:none !important;">
<div id="mpe-cal-wrap" class="hidden relative z-[1] shrink-0 px-5 pt-3 pb-3 bg-[rgb(var(--app-bg-rgb))]" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important;">
<div id="mpe-cal-section" class="hidden">
${createCalendarTopbarHTML({
prevId: 'mpe-cal-prev',
@@ -66,12 +66,12 @@ export function getMealPlanEditorHTML() {
<div id="mpe-slot-chips" class="flex flex-wrap gap-1.5"></div>
</div>
</div>
<div id="mpe-summary-wrap" class="relative z-[1] shrink-0 px-5 pb-3 bg-[#2d2e2b]" style="background:#2d2e2b !important; background-image:none !important;">
<div id="mpe-summary-wrap" class="relative z-[1] shrink-0 px-5 pb-3 bg-[rgb(var(--app-bg-rgb))]" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important;">
<div id="mpe-nutrition-section"></div>
<div id="mpe-servings-row" class="mt-3"></div>
<div id="mpe-top-shadow" class="pointer-events-none absolute inset-x-0 -bottom-3 h-3 opacity-0 transition-opacity duration-200" style="background:linear-gradient(to bottom, rgba(0,0,0,0.12), rgba(0,0,0,0.03), rgba(0,0,0,0));"></div>
</div>
<div id="mpe-ing-scroll" class="flex-1 min-h-0 overflow-y-auto no-scrollbar px-5 bg-[#2d2e2b]" style="background:#2d2e2b !important; background-image:none !important; padding-bottom:calc(1.5rem + env(safe-area-inset-bottom));">
<div id="mpe-ing-scroll" class="flex-1 min-h-0 overflow-y-auto no-scrollbar px-5 bg-[rgb(var(--app-bg-rgb))]" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important; padding-bottom:calc(1.5rem + env(safe-area-inset-bottom));">
<div id="mpe-ing-section" class="mb-4">
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Składniki</p>
<div id="mpe-ing-list" class="space-y-1.5"></div>
@@ -214,9 +214,9 @@ export function setupMealPlanEditor() {
if (!r) return;
el.innerHTML = MEAL_SLOTS.filter((s) => r.allowedSlots.includes(s.id)).map((s) => {
const sel = s.id === S.slotId;
const bg = sel ? '#23221e' : '#2f2f2d';
const border = sel ? '#787876' : '#444442';
const text = sel ? '#f2efe8' : '#d7d2c8';
const bg = sel ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-soft-rgb))';
const border = sel ? 'rgb(var(--border-input-rgb))' : 'rgb(var(--card-strong-rgb))';
const text = sel ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-soft-rgb))';
return `<button type="button" class="mpe-slot-btn px-3 py-1.5 rounded-full border text-[12px] font-semibold transition-colors" data-slot-id="${s.id}" style="background:${bg} !important; background-image:none !important; box-shadow:none !important; border-color:${border} !important; color:${text} !important;">${esc(s.label)}</button>`;
}).join('');
}
@@ -245,12 +245,12 @@ export function setupMealPlanEditor() {
el.innerHTML = `
<div class="flex items-center justify-between gap-3">
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider">Porcje</p>
<div class="flex h-[2rem] w-[5.25rem] shrink-0 items-center gap-0.5 rounded-full border px-0.5" style="background:#2f2f2d;border-color:#444442;box-shadow:0 2px 8px rgba(0,0,0,0.25);">
<button type="button" id="mpe-serv-minus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[#d7d2c8] transition-colors" aria-label="Zmniejsz liczbę porcji">
<div class="flex h-[2rem] w-[5.25rem] shrink-0 items-center gap-0.5 rounded-full border px-0.5" style="background:rgb(var(--card-soft-rgb));border-color:rgb(var(--card-strong-rgb));box-shadow:0 2px 8px rgba(0,0,0,0.25);">
<button type="button" id="mpe-serv-minus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[rgb(var(--text-body-soft-rgb))] transition-colors" aria-label="Zmniejsz liczbę porcji">
<i class="fas fa-minus text-[10px]"></i>
</button>
<span id="mpe-serv-count" class="flex-1 h-full inline-flex items-center justify-center px-0.5 text-[12px] font-semibold leading-none text-[#d7d2c8] tabular-nums">${S.servings}</span>
<button type="button" id="mpe-serv-plus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[#d7d2c8] transition-colors" aria-label="Zwiększ liczbę porcji">
<span id="mpe-serv-count" class="flex-1 h-full inline-flex items-center justify-center px-0.5 text-[12px] font-semibold leading-none text-[rgb(var(--text-body-soft-rgb))] tabular-nums">${S.servings}</span>
<button type="button" id="mpe-serv-plus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[rgb(var(--text-body-soft-rgb))] transition-colors" aria-label="Zwiększ liczbę porcji">
<i class="fas fa-plus text-[10px]"></i>
</button>
</div>
@@ -269,7 +269,7 @@ export function setupMealPlanEditor() {
const removeBtn = (cls, attrs) =>
`<button type="button" class="${cls} shrink-0 w-6 h-6 rounded-full border border-gray-200 text-gray-300 hover:text-red-500 hover:border-red-200 hover:bg-red-50 flex items-center justify-center transition-colors" ${attrs}><i class="fas fa-minus text-[8px]"></i></button>`;
const ingredientRowClass = 'mpe-ing-row rounded-xl px-3 py-3';
const ingredientRowStyle = 'background:#393937 !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;';
const ingredientRowStyle = 'background:rgb(var(--card-rgb)) !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;';
for (const ing of r.ingredients) {
const id = ing.ingredientId;
@@ -317,12 +317,12 @@ export function setupMealPlanEditor() {
const isSel = eid === altId;
const checkbox = `
<span class="ml-auto self-center w-[18px] h-[18px] rounded-full shrink-0 flex items-center justify-center"
style="border:1.5px solid #56534f; background:transparent;">
${isSel ? '<i class="fas fa-check" style="color:#9b978f; font-size:8px; line-height:1; display:block; transform:translateY(0.5px);"></i>' : ''}
style="border:1.5px solid rgba(var(--border-input-rgb), 0.58); background:transparent;">
${isSel ? '<i class="fas fa-check" style="color:rgb(var(--text-dim-rgb)); font-size:8px; line-height:1; display:block; transform:translateY(0.5px);"></i>' : ''}
</span>`;
const n = nutFor(altId, disp, ing.unit);
const nLine = n ? `<div class="text-[10px] text-gray-400 mt-0.5 tabular-nums">${n.kcal} kcal · ${n.protein}g B · ${n.fat}g T · ${n.carbs}g W</div>` : '';
html += `<button type="button" class="mpe-alt-pick w-full text-left p-2.5 rounded-lg transition-all" style="background:#2f2f2d !important; background-image:none !important; border:none !important; box-shadow:none !important;" data-orig-id="${esc(id)}" data-alt-id="${esc(altId)}"><div class="flex items-center gap-3"><div class="min-w-0 flex-1"><div class="text-[11px] font-semibold text-gray-900">${esc(name)}</div>${nLine}</div>${checkbox}</div></button>`;
html += `<button type="button" class="mpe-alt-pick w-full text-left p-2.5 rounded-lg transition-all" style="background:rgb(var(--card-soft-rgb)) !important; background-image:none !important; border:none !important; box-shadow:none !important;" data-orig-id="${esc(id)}" data-alt-id="${esc(altId)}"><div class="flex items-center gap-3"><div class="min-w-0 flex-1"><div class="text-[11px] font-semibold text-gray-900">${esc(name)}</div>${nLine}</div>${checkbox}</div></button>`;
}
html += '</div>';
}
@@ -341,7 +341,7 @@ export function setupMealPlanEditor() {
const addedBadge = addedProduct
? `<div class="flex items-center gap-1 mt-0.5"><span class="text-[10px] text-emerald-400 truncate">${esc(addedProduct.name)}</span></div>`
: '';
html += `<div class="flex-1 min-w-0 mpe-open-product-card cursor-pointer" data-eid="${esc(a.ingredientId)}" data-pid="${esc(addedPid)}"><div class="flex items-center gap-1.5"><span class="text-[12px] font-semibold text-gray-900 truncate">${esc(name)}</span><span class="shrink-0 inline-flex items-center justify-center text-[#8f8b84]" title="Dodany składnik" aria-label="Dodany składnik"><i class="fas fa-plus text-[8px]"></i></span></div>${addedBadge}</div>`;
html += `<div class="flex-1 min-w-0 mpe-open-product-card cursor-pointer" data-eid="${esc(a.ingredientId)}" data-pid="${esc(addedPid)}"><div class="flex items-center gap-1.5"><span class="text-[12px] font-semibold text-gray-900 truncate">${esc(name)}</span><span class="shrink-0 inline-flex items-center justify-center text-[rgb(var(--text-faint-rgb))]" title="Dodany składnik" aria-label="Dodany składnik"><i class="fas fa-plus text-[8px]"></i></span></div>${addedBadge}</div>`;
html += `<button type="button" class="mpe-edit-amt shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-ing-id="${esc(a.ingredientId)}" data-type="added">`;
html += `<span class="text-[12px] font-semibold text-gray-900 tabular-nums">${fmtAmt(disp)}</span>`;
html += `<span class="text-[11px] text-gray-500">${esc(a.unit)}</span></button>`;
@@ -365,7 +365,7 @@ export function setupMealPlanEditor() {
const el = document.getElementById('mpe-add-area');
if (!el) return;
if (!S.addOpen) {
el.innerHTML = `<button type="button" id="mpe-add-btn" class="w-full py-2 rounded-xl border border-dashed text-[12px] font-semibold transition-colors" style="border-color:#444442; color:#9b978f; background:transparent !important; box-shadow:none !important; -webkit-tap-highlight-color:transparent;"><i class="fas fa-plus text-[10px] mr-1.5 opacity-70"></i>Dodaj składnik</button>`;
el.innerHTML = `<button type="button" id="mpe-add-btn" class="w-full py-2 rounded-xl border border-dashed text-[12px] font-semibold transition-colors" style="border-color:rgb(var(--card-strong-rgb)); color:rgb(var(--text-dim-rgb)); background:transparent !important; box-shadow:none !important; -webkit-tap-highlight-color:transparent;"><i class="fas fa-plus text-[10px] mr-1.5 opacity-70"></i>Dodaj składnik</button>`;
return;
}
const recipe = RECIPES[S.recipeId];
@@ -376,15 +376,15 @@ export function setupMealPlanEditor() {
const q = S.addQuery.toLowerCase().trim();
const avail = Object.values(INGREDIENTS).filter((i) => !usedIds.has(i.id) && (!q || i.name.toLowerCase().includes(q)));
el.innerHTML = `
<div class="rounded-xl p-3" style="background:#393937 !important; border:1px solid #444442;">
<div class="rounded-xl p-3" style="background:rgb(var(--card-rgb)) !important; border:1px solid rgb(var(--card-strong-rgb));">
<div class="flex items-center gap-2 mb-2">
<input type="text" id="mpe-add-search" class="flex-1 rounded-lg px-3 py-1.5 text-[12px] outline-none placeholder:text-[#8f8b84]" style="background:#2f2f2d !important; border:1px solid #444442; color:#ddd6ca;" placeholder="Szukaj składnika…" value="${esc(S.addQuery)}">
<button type="button" id="mpe-add-cancel" class="text-[11px] font-semibold px-2 py-1 transition-colors" style="color:#9b978f;">Anuluj</button>
<input type="text" id="mpe-add-search" class="flex-1 rounded-lg px-3 py-1.5 text-[12px] outline-none placeholder:text-[rgb(var(--text-faint-rgb))]" style="background:rgb(var(--card-soft-rgb)) !important; border:1px solid rgb(var(--card-strong-rgb)); color:rgb(var(--text-body-rgb));" placeholder="Szukaj składnika…" value="${esc(S.addQuery)}">
<button type="button" id="mpe-add-cancel" class="text-[11px] font-semibold px-2 py-1 transition-colors" style="color:rgb(var(--text-dim-rgb));">Anuluj</button>
</div>
<div class="max-h-40 overflow-y-auto space-y-1 no-scrollbar" id="mpe-add-results">
${avail.length === 0
? '<p class="rounded-lg px-2.5 py-3 text-[11px] text-center" style="background:#2f2f2d !important; color:#9b978f;">Brak wyników</p>'
: avail.slice(0, 20).map((i) => `<button type="button" class="mpe-add-pick w-full text-left px-3 py-3 rounded-lg transition-colors text-[12px] font-medium" style="background:#2f2f2d !important; color:#ddd6ca;" data-ing-id="${esc(i.id)}">${esc(i.name)}</button>`).join('')}
? '<p class="rounded-lg px-2.5 py-3 text-[11px] text-center" style="background:rgb(var(--card-soft-rgb)) !important; color:rgb(var(--text-dim-rgb));">Brak wyników</p>'
: avail.slice(0, 20).map((i) => `<button type="button" class="mpe-add-pick w-full text-left px-3 py-3 rounded-lg transition-colors text-[12px] font-medium" style="background:rgb(var(--card-soft-rgb)) !important; color:rgb(var(--text-body-rgb));" data-ing-id="${esc(i.id)}">${esc(i.name)}</button>`).join('')}
</div>
</div>`;
}
@@ -400,8 +400,8 @@ export function setupMealPlanEditor() {
const q = S.addQuery.toLowerCase().trim();
const avail = Object.values(INGREDIENTS).filter((i) => !usedIds.has(i.id) && (!q || i.name.toLowerCase().includes(q)));
results.innerHTML = avail.length === 0
? '<p class="rounded-lg px-2.5 py-3 text-[11px] text-center" style="background:#2f2f2d !important; color:#9b978f;">Brak wyników</p>'
: avail.slice(0, 20).map((i) => `<button type="button" class="mpe-add-pick w-full text-left px-3 py-3 rounded-lg transition-colors text-[12px] font-medium" style="background:#2f2f2d !important; color:#ddd6ca;" data-ing-id="${esc(i.id)}">${esc(i.name)}</button>`).join('');
? '<p class="rounded-lg px-2.5 py-3 text-[11px] text-center" style="background:rgb(var(--card-soft-rgb)) !important; color:rgb(var(--text-dim-rgb));">Brak wyników</p>'
: avail.slice(0, 20).map((i) => `<button type="button" class="mpe-add-pick w-full text-left px-3 py-3 rounded-lg transition-colors text-[12px] font-medium" style="background:rgb(var(--card-soft-rgb)) !important; color:rgb(var(--text-body-rgb));" data-ing-id="${esc(i.id)}">${esc(i.name)}</button>`).join('');
}
function renderIngredients() {
@@ -417,23 +417,23 @@ export function setupMealPlanEditor() {
if (!el) return;
const n = totalNutrition();
el.innerHTML = `
<div class="h-full pb-2 flex flex-col" style="background:#2d2e2b !important; background-image:none !important; box-shadow:none !important;">
<div class="h-full pb-2 flex flex-col" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important; box-shadow:none !important;">
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Wartości odżywcze</p>
<div class="flex-1 flex items-center">
<div class="grid grid-cols-4 gap-1.5 w-full">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-gray-100 tabular-nums leading-tight">${n.kcal}</p>
<p class="text-[9px] text-gray-500 font-medium">kcal</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-blue-400 tabular-nums leading-tight">${n.protein}g</p>
<p class="text-[9px] text-gray-500 font-medium">białko</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-amber-400 tabular-nums leading-tight">${n.fat}g</p>
<p class="text-[9px] text-gray-500 font-medium">tłuszcz</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-orange-400 tabular-nums leading-tight">${n.carbs}g</p>
<p class="text-[9px] text-gray-500 font-medium">węglowodany</p>
</div>
@@ -753,7 +753,7 @@ export function setupMealPlanEditor() {
const ingBase = S.overrides[origId] ?? recipeIng?.amount ?? 0;
const ingDisp = ingBase * S.servings;
const checkmark = (sel) => `<span class="ml-auto self-center w-[18px] h-[18px] rounded-full shrink-0 flex items-center justify-center" style="border:1.5px solid #56534f; background:transparent;">${sel ? '<i class="fas fa-check" style="color:#9b978f; font-size:8px; line-height:1; display:block; transform:translateY(0.5px);"></i>' : ''}</span>`;
const checkmark = (sel) => `<span class="ml-auto self-center w-[18px] h-[18px] rounded-full shrink-0 flex items-center justify-center" style="border:1.5px solid rgba(var(--border-input-rgb), 0.58); background:transparent;">${sel ? '<i class="fas fa-check" style="color:rgb(var(--text-dim-rgb)); font-size:8px; line-height:1; display:block; transform:translateY(0.5px);"></i>' : ''}</span>`;
let pickerHtml = '<div class="mpe-product-picker mt-2 ml-1 space-y-1">';
for (const p of products) {
@@ -766,7 +766,7 @@ export function setupMealPlanEditor() {
const f = g / 100;
const n = pNut ? { kcal: Math.round(pNut.kcal * f), protein: Math.round(pNut.protein * f * 10) / 10, fat: Math.round(pNut.fat * f * 10) / 10, carbs: Math.round(pNut.carbs * f * 10) / 10 } : null;
const nLine = n ? `<div class="text-[10px] text-gray-400 mt-0.5 tabular-nums">${n.kcal} kcal · ${n.protein}g B · ${n.fat}g T · ${n.carbs}g W</div>` : '';
pickerHtml += `<button type="button" class="mpe-prod-pick w-full text-left p-2.5 rounded-lg transition-all" style="background:#2f2f2d !important; background-image:none !important; border:none !important; box-shadow:none !important;" data-eid="${esc(eid)}" data-prod-id="${esc(p.id)}">
pickerHtml += `<button type="button" class="mpe-prod-pick w-full text-left p-2.5 rounded-lg transition-all" style="background:rgb(var(--card-soft-rgb)) !important; background-image:none !important; border:none !important; box-shadow:none !important;" data-eid="${esc(eid)}" data-prod-id="${esc(p.id)}">
<div class="flex items-center gap-3"><div class="min-w-0 flex-1"><div class="text-[11px] font-semibold text-gray-900">${esc(p.name)}</div>${nLine}</div>${checkmark(isSel)}</div>
</button>`;
}

View File

@@ -33,22 +33,22 @@ function renderRecipeCard(recipe, { showSlotLabels = true, cardClassName = '' }
const className = ['recipe-browser-card', cardClassName].filter(Boolean).join(' ');
return `
<button type="button" data-recipe-id="${escapeHtml(recipe.id)}" class="${className} rounded-xl overflow-hidden flex flex-col bg-[#393937] cursor-pointer text-left transition-shadow" style="background:#393937 !important; border:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.28) !important;">
<div class="recipe-browser-card-media h-32 bg-[#d4d4d4] relative overflow-hidden">
<button type="button" data-recipe-id="${escapeHtml(recipe.id)}" class="${className} rounded-xl overflow-hidden flex flex-col bg-[rgb(var(--card-rgb))] cursor-pointer text-left transition-shadow" style="background:rgb(var(--card-rgb)) !important; border:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.28) !important;">
<div class="recipe-browser-card-media h-32 bg-[rgb(var(--skeleton-rgb))] relative overflow-hidden">
${recipe.image
? `<img src="${escapeHtml(recipe.image)}" alt="${escapeHtml(recipe.title)}" class="w-full h-full object-cover">`
: `<span class="absolute inset-0 flex items-center justify-center text-white font-medium text-xs">${escapeHtml(recipe.thumbLabel)}</span>`}
</div>
<div class="recipe-browser-card-body p-3 flex flex-col flex-1">
<h3 class="recipe-browser-card-title text-sm font-medium underline decoration-1 underline-offset-2 text-[#f1ede4] mb-3 line-clamp-2">${escapeHtml(recipe.title)}</h3>
<h3 class="recipe-browser-card-title text-sm font-medium underline decoration-1 underline-offset-2 text-[rgb(var(--text-primary-rgb))] mb-3 line-clamp-2">${escapeHtml(recipe.title)}</h3>
<div class="recipe-browser-card-footer mt-auto">
<div class="recipe-browser-card-meta flex items-center justify-between text-[11px] text-[#c2bcb2] font-medium mb-2">
<div class="flex items-center gap-1"><i class="fas fa-clock text-[#8f8b84]" aria-hidden="true"></i><span>${recipe.minutes} min</span></div>
<div class="flex items-center gap-1"><i class="fas fa-fire text-[#8f8b84]" aria-hidden="true"></i><span>${recipe.nutritionPerServing.kcal} kcal</span></div>
<div class="recipe-browser-card-meta flex items-center justify-between text-[11px] text-[rgb(var(--text-muted-rgb))] font-medium mb-2">
<div class="flex items-center gap-1"><i class="fas fa-clock text-[rgb(var(--text-faint-rgb))]" aria-hidden="true"></i><span>${recipe.minutes} min</span></div>
<div class="flex items-center gap-1"><i class="fas fa-fire text-[rgb(var(--text-faint-rgb))]" aria-hidden="true"></i><span>${recipe.nutritionPerServing.kcal} kcal</span></div>
</div>
${labels.length > 0
? `<div class="recipe-browser-card-labels flex flex-wrap gap-1">
${labels.map((label) => `<span class="recipe-browser-card-label px-2 py-0.5 bg-[#2f2f2d] text-[#d7d2c8] text-[10px] rounded-md font-medium">${escapeHtml(label)}</span>`).join('')}
${labels.map((label) => `<span class="recipe-browser-card-label px-2 py-0.5 bg-[rgb(var(--card-soft-rgb))] text-[rgb(var(--text-body-soft-rgb))] text-[10px] rounded-md font-medium">${escapeHtml(label)}</span>`).join('')}
</div>`
: ''}
</div>
@@ -71,14 +71,14 @@ export function getRecipeGridSectionHTML({
scrollId,
gridId,
emptyStateId,
scrollClassName = 'relative flex-1 overflow-y-auto px-4 pt-20 pb-24 bg-[#2d2e2b]',
gridClassName = 'grid grid-cols-2 gap-3 bg-[#2d2e2b]',
scrollClassName = 'relative flex-1 overflow-y-auto px-4 pt-20 pb-24 bg-[rgb(var(--app-bg-rgb))]',
gridClassName = 'grid grid-cols-2 gap-3 bg-[rgb(var(--app-bg-rgb))]',
emptyTitle = 'Brak wyników',
emptyMessage = 'Zmień kryteria wyszukiwania lub filtry',
} = {}) {
return `
<div id="${escapeHtml(scrollId)}" class="${scrollClassName}" style="background:#2d2e2b !important;">
<div id="${escapeHtml(gridId)}" class="${gridClassName}" style="background:#2d2e2b !important;"></div>
<div id="${escapeHtml(scrollId)}" class="${scrollClassName}" style="background:rgb(var(--app-bg-rgb)) !important;">
<div id="${escapeHtml(gridId)}" class="${gridClassName}" style="background:rgb(var(--app-bg-rgb)) !important;"></div>
${getEmptyStateHTML({
emptyStateId,
title: emptyTitle,

View File

@@ -27,11 +27,11 @@ export function getRecipeSearchFieldHTML({
const ariaLabel = inputAriaLabel || placeholder;
return `
<div id="${escapeHtml(shellId)}" class="relative z-[1] mx-auto flex items-center w-full overflow-hidden" style="width:min(calc(100% - 0.5rem), 22.4rem); background:#393937 !important; border:1px solid #41423f !important; border-radius:999px !important; box-shadow:${RECIPE_SEARCH_SHELL_BASE_SHADOW} !important; transition:box-shadow 180ms ease;">
<div id="${escapeHtml(shellId)}" class="relative z-[1] mx-auto flex items-center w-full overflow-hidden" style="width:min(calc(100% - 0.5rem), 22.4rem); background:rgb(var(--card-rgb)) !important; border:1px solid rgb(var(--border-card-rgb)) !important; border-radius:999px !important; box-shadow:${RECIPE_SEARCH_SHELL_BASE_SHADOW} !important; transition:box-shadow 180ms ease;">
<input type="text" id="${escapeHtml(inputId)}" value="${escapeHtml(inputValue)}" placeholder="${escapeHtml(placeholder)}" aria-label="${escapeHtml(ariaLabel)}" class="w-full bg-transparent outline-none text-[15px] text-center py-[12px] ${inputPadding}" style="background:transparent !important; border:none !important; box-shadow:none !important; backdrop-filter:none !important;">
${hasFilterButton
? `
<button id="${escapeHtml(filterButtonId)}"${actionAttr} class="absolute right-2 top-1/2 -translate-y-1/2 w-9 h-9 text-[#c9c3b8] hover:text-[#f0e8dc] flex items-center justify-center transition-colors" style="background:transparent !important; border:none !important; box-shadow:none !important;" aria-label="${escapeHtml(filterButtonLabel)}">
<button id="${escapeHtml(filterButtonId)}"${actionAttr} class="absolute right-2 top-1/2 -translate-y-1/2 w-9 h-9 text-[rgb(var(--text-body-soft-rgb))] hover:text-[rgb(var(--text-emphasis-rgb))] flex items-center justify-center transition-colors" style="background:transparent !important; border:none !important; box-shadow:none !important;" aria-label="${escapeHtml(filterButtonLabel)}">
<i class="fas fa-sliders-h" aria-hidden="true"></i>
</button>`
: ''}

View File

@@ -3,15 +3,15 @@ import { MEAL_SLOTS } from '../planner/mealSlots.js';
import { applyFilters, getFilterState } from './RecipeList.js';
const FILTER_PANEL_TRANSITION = 'opacity 180ms ease, transform 180ms ease';
const FILTER_SURFACE = '#23221e';
const FILTER_SURFACE_SOFT = '#2d2e2b';
const FILTER_BORDER = '#787876';
const FILTER_CHIP_ACTIVE_BG = '#393937';
const FILTER_TEXT_SECONDARY = '#d7d2c8';
const FILTER_TEXT_MUTED = '#b5afa5';
const FILTER_TEXT_ACTIVE = '#f2efe8';
const FILTER_TRACK = '#393937';
const FILTER_TRACK_FILL = '#56534f';
const FILTER_SURFACE = 'rgb(var(--sunken-rgb))';
const FILTER_SURFACE_SOFT = 'rgb(var(--app-bg-rgb))';
const FILTER_BORDER = 'rgb(var(--border-input-rgb))';
const FILTER_CHIP_ACTIVE_BG = 'rgb(var(--card-rgb))';
const FILTER_TEXT_SECONDARY = 'rgb(var(--text-body-soft-rgb))';
const FILTER_TEXT_MUTED = 'rgb(var(--text-muted-rgb))';
const FILTER_TEXT_ACTIVE = 'rgb(var(--text-emphasis-rgb))';
const FILTER_TRACK = 'rgb(var(--card-rgb))';
const FILTER_TRACK_FILL = 'rgba(var(--border-input-rgb), 0.58)';
const FILTER_SHADOW = '0 5px 10px rgba(0,0,0,0.16), 0 14px 22px rgba(0,0,0,0.24), 0 22px 34px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04)';
const PREP_TIME_MIN = 5;
const PREP_TIME_MAX = 120;
@@ -72,7 +72,7 @@ export function getFilterHTML() {
width: 1rem;
height: 1rem;
border-radius: 9999px;
border: 1px solid rgba(242,239,232,0.16);
border: 1px solid rgba(var(--text-emphasis-rgb),0.16);
background: ${FILTER_TRACK_FILL};
box-shadow: 0 0 0 1px rgba(0,0,0,0.12);
touch-action: none;
@@ -374,9 +374,9 @@ function syncPanelCount() {
if (button) {
const highlight = isFilterPanelOpen() || count > 0;
button.style.setProperty('background', highlight ? '#23221e' : '#393937', 'important');
button.style.setProperty('border-color', highlight ? '#787876' : '#41423f', 'important');
button.style.setProperty('color', highlight ? '#f2efe8' : '#ddd6ca', 'important');
button.style.setProperty('background', highlight ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-rgb))', 'important');
button.style.setProperty('border-color', highlight ? 'rgb(var(--border-input-rgb))' : 'rgb(var(--border-card-rgb))', 'important');
button.style.setProperty('color', highlight ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-rgb))', 'important');
}
const badge = button?.querySelector('[id$="-filter-count"]');

View File

@@ -70,10 +70,10 @@ function syncTodayButton(mode, weekStart, monthAnchor, selected) {
export function getMealPlannerHTML() {
return `
<div id="planner-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden bg-[#2d2e2b] z-10">
<div id="planner-cal-bar" class="shrink-0 bg-[#2d2e2b] border-b border-[#444442] mt-3 relative z-10">
<div id="planner-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden bg-[rgb(var(--app-bg-rgb))] z-10">
<div id="planner-cal-bar" class="shrink-0 bg-[rgb(var(--app-bg-rgb))] border-b border-[rgb(var(--card-strong-rgb))] mt-3 relative z-10">
<div class="min-h-12 px-4 pt-4 pb-3 flex items-center justify-between gap-3 min-w-0">
<h1 class="min-w-0 flex-1 truncate" style="margin:0;padding:0;color:#f2efe8;font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Plan posiłków</h1>
<h1 class="min-w-0 flex-1 truncate" style="margin:0;padding:0;color:rgb(var(--text-emphasis-rgb));font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Plan posiłków</h1>
${createCalendarTopbarHTML({
prevId: 'cal-prev',
todayId: 'cal-go-today',
@@ -81,12 +81,12 @@ export function getMealPlannerHTML() {
wrapperClass: 'flex shrink-0 items-center justify-end',
})}
</div>
<div id="calendar-swipe-zone" class="overflow-x-hidden bg-[#2d2e2b]" style="touch-action: none">
<div id="calendar-week-wrap" class="px-3 overflow-x-hidden bg-[#2d2e2b]" style="overflow: hidden; max-height: 10rem; opacity: 1; padding-bottom: 0.75rem">
<div id="calendar-swipe-zone" class="overflow-x-hidden bg-[rgb(var(--app-bg-rgb))]" style="touch-action: none">
<div id="calendar-week-wrap" class="px-3 overflow-x-hidden bg-[rgb(var(--app-bg-rgb))]" style="overflow: hidden; max-height: 10rem; opacity: 1; padding-bottom: 0.75rem">
${createCalendarWeekdayHeaderHTML()}
<div id="calendar-week-grid" class="grid grid-cols-7 gap-1.5 max-w-full overflow-x-hidden"></div>
</div>
<div id="calendar-month-wrap" class="px-3 bg-[#2d2e2b]" style="overflow: hidden; max-height: 0; opacity: 0; padding-bottom: 0">
<div id="calendar-month-wrap" class="px-3 bg-[rgb(var(--app-bg-rgb))]" style="overflow: hidden; max-height: 0; opacity: 0; padding-bottom: 0">
${createCalendarWeekdayHeaderHTML()}
<div id="calendar-month-grid" class="grid grid-cols-7 gap-1.5"></div>
</div>
@@ -96,25 +96,25 @@ export function getMealPlannerHTML() {
</div>
</div>
<div id="planner-scroll" class="flex-1 overflow-y-auto px-4 pt-3 pb-24 bg-[#2d2e2b]">
<div id="planner-scroll" class="flex-1 overflow-y-auto px-4 pt-3 pb-24 bg-[rgb(var(--app-bg-rgb))]">
<div id="planner-summary-card" class="mb-3">
<div class="h-full flex flex-col" style="background:#2d2e2b !important; background-image:none !important; box-shadow:none !important;">
<div class="h-full flex flex-col" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important; box-shadow:none !important;">
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Wartości odżywcze</p>
<div class="flex-1 flex items-center">
<div class="grid grid-cols-4 gap-1.5 w-full">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p id="planner-nutrition-kcal" class="text-[15px] font-bold text-gray-100 tabular-nums leading-tight">—</p>
<p class="text-[9px] text-gray-500 font-medium">kcal</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p id="planner-nutrition-p" class="text-[15px] font-bold text-blue-400 tabular-nums leading-tight">—</p>
<p class="text-[9px] text-gray-500 font-medium">białko</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p id="planner-nutrition-f" class="text-[15px] font-bold text-amber-400 tabular-nums leading-tight">—</p>
<p class="text-[9px] text-gray-500 font-medium">tłuszcz</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p id="planner-nutrition-c" class="text-[15px] font-bold text-orange-400 tabular-nums leading-tight">—</p>
<p class="text-[9px] text-gray-500 font-medium">węglowodany</p>
</div>
@@ -122,19 +122,19 @@ export function getMealPlannerHTML() {
</div>
</div>
</div>
<button type="button" id="planner-open-ingredients" class="hidden w-full mb-3 flex items-center justify-center gap-2 py-2.5 rounded-xl border border-dashed border-[#444442] bg-[#2d2e2b] text-[13px] font-semibold text-[#d7d2c8] hover:border-[#6d6c67] hover:bg-[#3a3a37] transition-colors">
<i class="fas fa-shopping-basket text-[#9b978f] text-xs" aria-hidden="true"></i>
<button type="button" id="planner-open-ingredients" class="hidden w-full mb-3 flex items-center justify-center gap-2 py-2.5 rounded-xl border border-dashed border-[rgb(var(--card-strong-rgb))] bg-[rgb(var(--app-bg-rgb))] text-[13px] font-semibold text-[rgb(var(--text-body-soft-rgb))] hover:border-[rgb(var(--text-subdued-rgb))] hover:bg-[rgb(var(--card-raised-rgb))] transition-colors">
<i class="fas fa-shopping-basket text-[rgb(var(--text-dim-rgb))] text-xs" aria-hidden="true"></i>
Składniki na ten dzień
</button>
<div id="planner-meal-slots" class="space-y-3 pb-2 bg-[#2d2e2b]"></div>
<div id="planner-meal-slots" class="space-y-3 pb-2 bg-[rgb(var(--app-bg-rgb))]"></div>
</div>
</div>
<div id="planner-picker-backdrop" class="absolute inset-0 z-[55] bg-black/45 hidden opacity-0 transition-opacity duration-200" aria-hidden="true"></div>
<div id="planner-picker-sheet" data-off-transform="${PLANNER_PICKER_OFF_TRANSFORM}" class="absolute inset-x-0 bottom-0 z-[60] flex flex-col will-change-transform rounded-t-[1.85rem] overflow-hidden" style="top:calc(env(safe-area-inset-top) + 0.35rem); visibility: hidden; transform: ${PLANNER_PICKER_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1); background:#2d2e2b !important; background-image:none !important;" role="dialog" aria-label="Wybierz przepis" aria-modal="true">
<div id="planner-picker-sheet" data-off-transform="${PLANNER_PICKER_OFF_TRANSFORM}" class="absolute inset-x-0 bottom-0 z-[60] flex flex-col will-change-transform rounded-t-[1.85rem] overflow-hidden" style="top:calc(env(safe-area-inset-top) + 0.35rem); visibility: hidden; transform: ${PLANNER_PICKER_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1); background:rgb(var(--app-bg-rgb)) !important; background-image:none !important;" role="dialog" aria-label="Wybierz przepis" aria-modal="true">
<div class="pointer-events-none absolute inset-x-0 top-0 z-[2] px-4 pt-3">
<div class="pointer-events-auto pb-4 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć">
<div class="w-10 h-1 bg-[#6d6c67]/75 rounded-full mx-auto" aria-hidden="true"></div>
<div class="w-10 h-1 bg-[rgb(var(--text-subdued-rgb))]/75 rounded-full mx-auto" aria-hidden="true"></div>
</div>
<div class="pointer-events-auto">
${getRecipeSearchFieldHTML({
@@ -148,8 +148,8 @@ export function getMealPlannerHTML() {
})}
</div>
</div>
<div id="planner-picker-scroll" class="relative min-h-0 flex-1 overflow-y-auto px-4 pt-28 pb-8 bg-[#2d2e2b]" style="background:#2d2e2b !important;">
<div id="planner-picker-grid" class="grid grid-cols-3 gap-2 bg-[#2d2e2b]" style="background:#2d2e2b !important;"></div>
<div id="planner-picker-scroll" class="relative min-h-0 flex-1 overflow-y-auto px-4 pt-28 pb-8 bg-[rgb(var(--app-bg-rgb))]" style="background:rgb(var(--app-bg-rgb)) !important;">
<div id="planner-picker-grid" class="grid grid-cols-3 gap-2 bg-[rgb(var(--app-bg-rgb))]" style="background:rgb(var(--app-bg-rgb)) !important;"></div>
<div id="planner-picker-empty-state" class="hidden flex flex-col items-center justify-center py-16 text-center">
<div class="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mb-4">
<i class="fas fa-search text-2xl text-gray-300" aria-hidden="true"></i>
@@ -161,20 +161,20 @@ export function getMealPlannerHTML() {
</div>
<div id="planner-ing-backdrop" class="absolute inset-0 z-[55] bg-black/45 hidden opacity-0 transition-opacity duration-200" aria-hidden="true"></div>
<div id="planner-ing-sheet" class="absolute left-0 right-0 bottom-0 z-[60] rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; height: auto; max-height: calc(100vh - env(safe-area-inset-top) - 1rem); transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1); background:#2d2e2b !important; background-image:none !important;" role="dialog" aria-labelledby="planner-ing-title" aria-modal="true">
<div class="shrink-0 px-4 pt-3 pb-2 border-b border-[#444442] touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć">
<div class="w-10 h-1 bg-[#6d6c67]/75 rounded-full mx-auto mb-2.5" aria-hidden="true"></div>
<h2 id="planner-ing-title" class="text-[15px] font-bold text-[#ddd6ca] leading-tight pr-2">Składniki i spiżarnia</h2>
<p id="planner-ing-sub" class="text-[11px] text-[#9b978f] mt-1">Porównanie potrzeb z zapasami.</p>
<div id="planner-ing-sheet" class="absolute left-0 right-0 bottom-0 z-[60] rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; height: auto; max-height: calc(100vh - env(safe-area-inset-top) - 1rem); transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1); background:rgb(var(--app-bg-rgb)) !important; background-image:none !important;" role="dialog" aria-labelledby="planner-ing-title" aria-modal="true">
<div class="shrink-0 px-4 pt-3 pb-2 border-b border-[rgb(var(--card-strong-rgb))] touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć">
<div class="w-10 h-1 bg-[rgb(var(--text-subdued-rgb))]/75 rounded-full mx-auto mb-2.5" aria-hidden="true"></div>
<h2 id="planner-ing-title" class="text-[15px] font-bold text-[rgb(var(--text-body-rgb))] leading-tight pr-2">Składniki i spiżarnia</h2>
<p id="planner-ing-sub" class="text-[11px] text-[rgb(var(--text-dim-rgb))] mt-1">Porównanie potrzeb z zapasami.</p>
</div>
<div id="planner-ing-body" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2 pb-2"></div>
<div id="planner-ing-footer" class="shrink-0 p-4 pt-2 pb-5 border-t border-[#444442] bg-[#2d2e2b] space-y-2">
<div id="planner-ing-footer" class="shrink-0 p-4 pt-2 pb-5 border-t border-[rgb(var(--card-strong-rgb))] bg-[rgb(var(--app-bg-rgb))] space-y-2">
<button type="button" id="planner-ing-add-all" class="w-full bg-gray-900 hover:bg-black text-white py-3 rounded-xl font-semibold shadow-sm transition-colors text-[13px] flex items-center justify-center gap-2">
<i class="fas fa-cart-plus text-xs" aria-hidden="true"></i>
Dodaj braki na dziś do listy
</button>
<button type="button" id="planner-ing-add-btn" class="hidden w-full border border-[#444442] bg-[#2d2e2b] text-[#d7d2c8] hover:bg-[#3a3a37] py-2.5 rounded-xl font-semibold text-[13px] flex items-center justify-center gap-2 transition-colors">
<i class="fas fa-calendar-week text-[#9b978f] text-[11px]" aria-hidden="true"></i>
<button type="button" id="planner-ing-add-btn" class="hidden w-full border border-[rgb(var(--card-strong-rgb))] bg-[rgb(var(--app-bg-rgb))] text-[rgb(var(--text-body-soft-rgb))] hover:bg-[rgb(var(--card-raised-rgb))] py-2.5 rounded-xl font-semibold text-[13px] flex items-center justify-center gap-2 transition-colors">
<i class="fas fa-calendar-week text-[rgb(var(--text-dim-rgb))] text-[11px]" aria-hidden="true"></i>
Dodaj braki na cały tydzień
</button>
</div>
@@ -469,27 +469,27 @@ function renderDayContent(state, onMealRemoved = null) {
(entry.addedIngredients?.length > 0) ||
(entry.substitutions && Object.keys(entry.substitutions).length > 0);
const customDot = hasCustom ? '<span class="w-1.5 h-1.5 rounded-full bg-amber-400 inline-block shrink-0 ml-1"></span>' : '';
const servLabel = servings > 1 ? `<span class="mx-1.5 text-[#6d6c67]">·</span>×${servings}` : '';
const servLabel = servings > 1 ? `<span class="mx-1.5 text-[rgb(var(--text-subdued-rgb))]">·</span>×${servings}` : '';
const rowStyle = `--planner-swipe-progress:${isPendingDelete ? '1' : '0'};`;
const rowAttrs = isPendingDelete ? 'data-pending-delete="true"' : '';
const backgroundStyle = isPendingDelete
? 'background:linear-gradient(90deg, rgba(65,64,60,0.08), rgba(65,64,60,0.2));'
: 'background:rgba(203,74,72, calc(0.18 + var(--planner-swipe-progress) * 0.5));';
? 'background:linear-gradient(90deg, rgba(var(--border-card-rgb),0.08), rgba(var(--border-card-rgb),0.2));'
: 'background:rgba(var(--danger-rgb), calc(0.18 + var(--planner-swipe-progress) * 0.5));';
const backgroundLabel = isPendingDelete
? `<span class="inline-flex items-center gap-1.5 text-[11px] font-semibold tracking-wide uppercase" style="color:rgba(210,204,194,0.42);">
? `<span class="inline-flex items-center gap-1.5 text-[11px] font-semibold tracking-wide uppercase" style="color:rgba(var(--text-body-soft-rgb),0.42);">
<i class="fas fa-hourglass-half text-[10px]" aria-hidden="true"></i>
Usuwanie
</span>`
: `<span class="inline-flex items-center gap-1.5 text-[11px] font-semibold tracking-wide uppercase" style="color:rgba(250,234,234, calc(0.55 + var(--planner-swipe-progress) * 0.45));">
: `<span class="inline-flex items-center gap-1.5 text-[11px] font-semibold tracking-wide uppercase" style="color:rgba(var(--text-emphasis-rgb), calc(0.55 + var(--planner-swipe-progress) * 0.45));">
<i class="fas fa-trash text-[10px]" aria-hidden="true"></i>
Usuń
</span>`;
const titleClass = isPendingDelete
? 'text-[13px] font-normal text-[#bcb5ab] truncate'
: 'text-[13px] font-normal text-[#ddd6ca] truncate';
? 'text-[13px] font-normal text-[rgb(var(--text-muted-rgb))] truncate'
: 'text-[13px] font-normal text-[rgb(var(--text-body-rgb))] truncate';
const metaClass = isPendingDelete
? 'text-[11px] text-[#878079] mt-0.5 tabular-nums'
: 'text-[11px] text-[#9b978f] mt-0.5 tabular-nums';
? 'text-[11px] text-[rgb(var(--text-faint-rgb))] mt-0.5 tabular-nums'
: 'text-[11px] text-[rgb(var(--text-dim-rgb))] mt-0.5 tabular-nums';
const actionWrapClass = isPendingDelete
? 'relative z-[2] flex items-center shrink-0 self-center'
: 'flex items-center gap-1 shrink-0 self-center';
@@ -501,11 +501,11 @@ function renderDayContent(state, onMealRemoved = null) {
: 0;
const entryAction = isPendingDelete
? `<button type="button" class="planner-cancel-pending-remove rounded-full p-[1.5px] transition-transform hover:scale-[1.02] active:scale-[0.98]" style="background:${getPendingMealRemovalButtonStyle(remainingProgress)};" data-pending-delete-progress data-day-key="${selectedDayKey}" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Anuluj usunięcie posiłku">
<span class="flex h-7 w-7 items-center justify-center rounded-full bg-[#343530] text-[#e2ddd4] shadow-[0_1px_2px_rgba(0,0,0,0.28)]">
<span class="flex h-7 w-7 items-center justify-center rounded-full bg-[rgb(var(--card-raised-rgb))] text-[rgb(var(--text-body-rgb))] shadow-[0_1px_2px_rgba(0,0,0,0.28)]">
<i class="fas fa-rotate-left text-[10px]" aria-hidden="true"></i>
</span>
</button>`
: `<button type="button" class="planner-edit-meal w-6 h-6 rounded-full border border-[#444442] text-[#9b978f] hover:text-[#ddd6ca] hover:border-[#6d6c67] hover:bg-[#3a3a37] flex items-center justify-center transition-colors" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Edytuj ten przepis">
: `<button type="button" class="planner-edit-meal w-6 h-6 rounded-full border border-[rgb(var(--card-strong-rgb))] text-[rgb(var(--text-dim-rgb))] hover:text-[rgb(var(--text-body-rgb))] hover:border-[rgb(var(--text-subdued-rgb))] hover:bg-[rgb(var(--card-raised-rgb))] flex items-center justify-center transition-colors" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Edytuj ten przepis">
<i class="fas fa-pencil text-[9px]" aria-hidden="true"></i>
</button>`;
return `
@@ -513,10 +513,10 @@ function renderDayContent(state, onMealRemoved = null) {
<div class="pointer-events-none absolute inset-0 flex items-center justify-end px-4" style="${backgroundStyle}">
${backgroundLabel}
</div>
<div class="relative z-[1] rounded-lg p-2 planner-open-recipe cursor-pointer" style="background:${isPendingDelete ? 'rgba(45,45,43,0.76)' : '#2d2e2b'}; box-shadow:inset 0 1px 3px rgba(0,0,0,0.3); transform:${isPendingDelete ? 'translateX(0) scale(0.988)' : 'translateX(0)'}; transition:transform 180ms cubic-bezier(0.22, 1, 0.36, 1), opacity 180ms ease, background-color 180ms ease; touch-action:pan-y; opacity:1;" data-planner-swipe-card data-slot-id="${slot.id}" data-entry-id="${eid}" data-recipe-id="${escapeHtml(recipe.id)}">
<div class="relative z-[1] rounded-lg p-2 planner-open-recipe cursor-pointer" style="background:${isPendingDelete ? 'rgba(var(--app-bg-rgb),0.76)' : 'rgb(var(--app-bg-rgb))'}; box-shadow:inset 0 1px 3px rgba(0,0,0,0.3); transform:${isPendingDelete ? 'translateX(0) scale(0.988)' : 'translateX(0)'}; transition:transform 180ms cubic-bezier(0.22, 1, 0.36, 1), opacity 180ms ease, background-color 180ms ease; touch-action:pan-y; opacity:1;" data-planner-swipe-card data-slot-id="${slot.id}" data-entry-id="${eid}" data-recipe-id="${escapeHtml(recipe.id)}">
<div class="relative flex items-start justify-between gap-2">
<div class="flex items-center gap-2 min-w-0" style="${contentToneStyle}">
<div class="w-8 h-8 rounded-lg bg-[#3a3a37] overflow-hidden shrink-0">
<div class="w-8 h-8 rounded-lg bg-[rgb(var(--card-raised-rgb))] overflow-hidden shrink-0">
${recipe.image
? `<img src="${escapeHtml(recipe.image)}" alt="" class="w-full h-full object-cover">`
: `<span class="w-full h-full flex items-center justify-center text-white text-[8px] font-medium">${escapeHtml(recipe.thumbLabel)}</span>`}
@@ -524,9 +524,9 @@ function renderDayContent(state, onMealRemoved = null) {
<div class="min-w-0">
<div class="flex items-center"><p class="${titleClass}">${escapeHtml(recipe.title)}</p>${customDot}</div>
<p class="${metaClass}">
<i class="fas fa-clock text-[#7d7a74] mr-0.5" aria-hidden="true"></i>${recipe.minutes} min
<span class="mx-1.5 text-[#6d6c67]">·</span>
<i class="fas fa-fire text-[#7d7a74] mr-0.5" aria-hidden="true"></i>${entryN.kcal} kcal${servLabel}
<i class="fas fa-clock text-[rgb(var(--text-faint-rgb))] mr-0.5" aria-hidden="true"></i>${recipe.minutes} min
<span class="mx-1.5 text-[rgb(var(--text-subdued-rgb))]">·</span>
<i class="fas fa-fire text-[rgb(var(--text-faint-rgb))] mr-0.5" aria-hidden="true"></i>${entryN.kcal} kcal${servLabel}
</p>
</div>
</div>
@@ -538,36 +538,36 @@ function renderDayContent(state, onMealRemoved = null) {
</div>`;
}).join('');
const addBtn = `<button type="button" class="planner-add-meal w-7 h-7 rounded-full border border-[#444442] text-[#9b978f] flex items-center justify-center shrink-0" data-slot-id="${slot.id}" aria-label="Dodaj przepis"><i class="fas fa-plus text-[10px]"></i></button>`;
const addBtn = `<button type="button" class="planner-add-meal w-7 h-7 rounded-full border border-[rgb(var(--card-strong-rgb))] text-[rgb(var(--text-dim-rgb))] flex items-center justify-center shrink-0" data-slot-id="${slot.id}" aria-label="Dodaj przepis"><i class="fas fa-plus text-[10px]"></i></button>`;
const kcalPill = slotKcal > 0
? `<span class="text-[10px] font-semibold tabular-nums shrink-0 px-2 py-0.5 rounded-full" style="background:#2d2e2b; color:#d7d2c8;">${slotKcal} kcal</span>`
? `<span class="text-[10px] font-semibold tabular-nums shrink-0 px-2 py-0.5 rounded-full" style="background:rgb(var(--app-bg-rgb)); color:rgb(var(--text-body-soft-rgb));">${slotKcal} kcal</span>`
: '';
const filledCard = `
<div class="rounded-xl bg-[#393937] overflow-hidden" style="background:#393937 !important; box-shadow:0 2px 8px rgba(0,0,0,0.25);" data-slot-id="${slot.id}">
<div class="flex items-center gap-2 px-3 py-2.5 bg-[#393937]" style="background:#393937 !important;">
<i class="fas ${slot.icon} w-7 text-center text-[13px] text-[#9b978f] shrink-0" aria-hidden="true"></i>
<span class="text-[13px] font-semibold text-[#ddd6ca] truncate min-w-0">${slot.label}</span>
<div class="rounded-xl bg-[rgb(var(--card-rgb))] overflow-hidden" style="background:rgb(var(--card-rgb)) !important; box-shadow:0 2px 8px rgba(0,0,0,0.25);" data-slot-id="${slot.id}">
<div class="flex items-center gap-2 px-3 py-2.5 bg-[rgb(var(--card-rgb))]" style="background:rgb(var(--card-rgb)) !important;">
<i class="fas ${slot.icon} w-7 text-center text-[13px] text-[rgb(var(--text-dim-rgb))] shrink-0" aria-hidden="true"></i>
<span class="text-[13px] font-semibold text-[rgb(var(--text-body-rgb))] truncate min-w-0">${slot.label}</span>
<span class="ml-auto"></span>
${kcalPill}
${addBtn}
</div>
${entries.length > 0 ? `<div class="px-2.5 pb-2.5 space-y-2 border-t border-[#444442]" style="padding-top:0.625rem;">${entryCards}</div>` : ''}
${entries.length > 0 ? `<div class="px-2.5 pb-2.5 space-y-2 border-t border-[rgb(var(--card-strong-rgb))]" style="padding-top:0.625rem;">${entryCards}</div>` : ''}
</div>`;
if (entries.length > 0) return filledCard;
return `
<div class="rounded-xl bg-[#393937] overflow-hidden" style="background:#393937 !important; box-shadow:0 2px 8px rgba(0,0,0,0.25);" data-slot-id="${slot.id}">
<div class="rounded-xl bg-[rgb(var(--card-rgb))] overflow-hidden" style="background:rgb(var(--card-rgb)) !important; box-shadow:0 2px 8px rgba(0,0,0,0.25);" data-slot-id="${slot.id}">
<div class="flex items-center gap-2 px-3 py-2.5">
<i class="fas ${slot.icon} w-7 text-center text-[13px] text-[#9b978f] shrink-0" aria-hidden="true"></i>
<span class="text-[13px] font-semibold text-[#ddd6ca] truncate min-w-0">${slot.label}</span>
<i class="fas ${slot.icon} w-7 text-center text-[13px] text-[rgb(var(--text-dim-rgb))] shrink-0" aria-hidden="true"></i>
<span class="text-[13px] font-semibold text-[rgb(var(--text-body-rgb))] truncate min-w-0">${slot.label}</span>
<span class="ml-auto"></span>
${addBtn}
</div>
<div class="px-3 pb-2.5 -mt-0.5">
<p class="text-[11px] text-[#7d7a74] italic pl-9">Zaplanuj posiłek</p>
<p class="text-[11px] text-[rgb(var(--text-faint-rgb))] italic pl-9">Zaplanuj posiłek</p>
</div>
</div>`;
}).join('');
@@ -594,7 +594,7 @@ function getPendingMealRemovalProgress(state, dayKey, slotId, entryId) {
function getPendingMealRemovalButtonStyle(progress) {
const clamped = Math.max(0, Math.min(1, progress));
const angle = Math.round(clamped * 360);
return `conic-gradient(from -90deg, rgba(229,223,214,0.96) 0deg, rgba(229,223,214,0.96) ${angle}deg, rgba(109,108,103,0.24) ${angle}deg, rgba(109,108,103,0.24) 360deg)`;
return `conic-gradient(from -90deg, rgba(var(--text-body-rgb),0.96) 0deg, rgba(var(--text-body-rgb),0.96) ${angle}deg, rgba(var(--text-subdued-rgb),0.24) ${angle}deg, rgba(var(--text-subdued-rgb),0.24) 360deg)`;
}
function stopPendingMealRemovalTicker(state) {
@@ -970,7 +970,7 @@ function renderIngredientsSheet(state) {
if (subEl) subEl.textContent = 'Porównanie potrzeb z zapasami w spiżarni.';
if (!today || today.items.length === 0) {
body.innerHTML = '<p class="text-sm text-[#9b978f] text-center py-8">Najpierw zaplanuj posiłki.</p>';
body.innerHTML = '<p class="text-sm text-[rgb(var(--text-dim-rgb))] text-center py-8">Najpierw zaplanuj posiłki.</p>';
updateIngButtons(state);
return;
}
@@ -980,8 +980,8 @@ function renderIngredientsSheet(state) {
let html = '';
if (shortItems.length === 0) {
html += `<div class="rounded-xl bg-[#2d2e2b] border border-emerald-400/40 p-3 mb-4 flex items-center gap-2.5">
<div class="w-8 h-8 rounded-full bg-[#24352a] flex items-center justify-center shrink-0">
html += `<div class="rounded-xl bg-[rgb(var(--app-bg-rgb))] border border-emerald-400/40 p-3 mb-4 flex items-center gap-2.5">
<div class="w-8 h-8 rounded-full bg-[rgba(var(--success-rgb), 0.14)] flex items-center justify-center shrink-0">
<i class="fas fa-check text-emerald-600 text-sm"></i>
</div>
<div>
@@ -990,8 +990,8 @@ function renderIngredientsSheet(state) {
</div>
</div>`;
} else {
html += `<div class="rounded-xl bg-[#2d2e2b] border border-red-300/40 p-3 mb-4 flex items-center gap-2.5">
<div class="w-8 h-8 rounded-full bg-[#3a2326] flex items-center justify-center shrink-0">
html += `<div class="rounded-xl bg-[rgb(var(--app-bg-rgb))] border border-red-300/40 p-3 mb-4 flex items-center gap-2.5">
<div class="w-8 h-8 rounded-full bg-[rgba(var(--danger-rgb), 0.14)] flex items-center justify-center shrink-0">
<i class="fas fa-exclamation text-red-500 text-sm"></i>
</div>
<div>
@@ -1006,15 +1006,15 @@ function renderIngredientsSheet(state) {
<p class="text-[10px] font-bold text-red-400 uppercase tracking-wider mb-2 px-0.5">
<i class="fas fa-cart-shopping text-[9px] mr-1"></i>Do kupienia
</p>
<ul class="border border-red-300/30 rounded-xl overflow-hidden bg-[#2d2e2b] divide-y divide-[#3a2326]">
<ul class="border border-red-300/30 rounded-xl overflow-hidden bg-[rgb(var(--app-bg-rgb))] divide-y divide-[rgba(var(--danger-rgb), 0.14)]">
${shortItems.map((ing) => `
<li class="flex items-start gap-3 py-3 px-3">
<div class="w-2 h-2 rounded-full bg-red-400 mt-1.5 shrink-0"></div>
<div class="flex-1 min-w-0">
<p class="text-[13px] font-semibold text-[#ddd6ca]">${escapeHtml(ing.name)}</p>
<p class="text-[11px] text-[#9b978f] mt-0.5">
potrzeba <span class="font-medium text-[#d7d2c8]">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
<span class="mx-1 text-[#6d6c67]">&middot;</span>
<p class="text-[13px] font-semibold text-[rgb(var(--text-body-rgb))]">${escapeHtml(ing.name)}</p>
<p class="text-[11px] text-[rgb(var(--text-dim-rgb))] mt-0.5">
potrzeba <span class="font-medium text-[rgb(var(--text-body-soft-rgb))]">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
<span class="mx-1 text-[rgb(var(--text-subdued-rgb))]">&middot;</span>
w spiżarni <span class="font-medium ${ing.pantryQty > 0 ? 'text-amber-600' : 'text-gray-400'}">${ing.pantryQty > 0 ? formatAmount(ing.pantryQty) + ' ' + escapeHtml(ing.pantryUnit) : 'brak'}</span>
</p>
</div>
@@ -1032,15 +1032,15 @@ function renderIngredientsSheet(state) {
<p class="text-[10px] font-bold text-emerald-500 uppercase tracking-wider mb-2 px-0.5">
<i class="fas fa-check text-[9px] mr-1"></i>W spiżarni
</p>
<ul class="border border-[#444442] rounded-xl overflow-hidden bg-[#2d2e2b] divide-y divide-[#353632]">
<ul class="border border-[rgb(var(--card-strong-rgb))] rounded-xl overflow-hidden bg-[rgb(var(--app-bg-rgb))] divide-y divide-[rgb(var(--card-raised-rgb))]">
${okItems.map((ing) => `
<li class="flex items-start gap-3 py-2.5 px-3">
<div class="w-2 h-2 rounded-full bg-emerald-400 mt-1.5 shrink-0"></div>
<div class="flex-1 min-w-0">
<p class="text-[13px] font-medium text-[#d7d2c8]">${escapeHtml(ing.name)}</p>
<p class="text-[11px] text-[#9b978f] mt-0.5">
potrzeba <span class="font-medium text-[#d7d2c8]">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
<span class="mx-1 text-[#6d6c67]">&middot;</span>
<p class="text-[13px] font-medium text-[rgb(var(--text-body-soft-rgb))]">${escapeHtml(ing.name)}</p>
<p class="text-[11px] text-[rgb(var(--text-dim-rgb))] mt-0.5">
potrzeba <span class="font-medium text-[rgb(var(--text-body-soft-rgb))]">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
<span class="mx-1 text-[rgb(var(--text-subdued-rgb))]">&middot;</span>
masz <span class="font-medium text-emerald-600">${formatAmount(ing.pantryQty)} ${escapeHtml(ing.pantryUnit)}</span>
</p>
</div>
@@ -1059,7 +1059,7 @@ function renderIngredientsSheet(state) {
const wd = WEEKDAYS_LONG[day.date.getDay()];
const label = `${wd}, ${day.date.getDate()} ${CALENDAR_MONTHS_SHORT[day.date.getMonth()]}`;
const shorts = day.items.filter((it) => !it.enough);
return `<div class="rounded-xl border border-amber-200/80 bg-[#2d2e2b] p-3">
return `<div class="rounded-xl border border-amber-200/80 bg-[rgb(var(--app-bg-rgb))] p-3">
<p class="text-[12px] font-semibold text-amber-900">
<i class="fas fa-calendar-day text-[10px] mr-1.5 text-amber-500"></i>${escapeHtml(label)}
</p>

View File

@@ -49,9 +49,9 @@ function getActivePantryFilterCount() {
}
function filterChipStyle(active) {
const background = active ? '#393937' : '#2d2e2b';
const color = active ? '#f2efe8' : '#d7d2c8';
const borderRule = active ? 'border:1px solid #787876;' : 'border:none;';
const background = active ? 'rgb(var(--card-rgb))' : 'rgb(var(--app-bg-rgb))';
const color = active ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-soft-rgb))';
const borderRule = active ? 'border:1px solid rgb(var(--border-input-rgb));' : 'border:none;';
const shadow = active
? 'box-shadow:inset 0 1px 0 rgba(255,255,255,0.04), 0 0 0 1px rgba(0,0,0,0.08);'
: '';
@@ -81,20 +81,20 @@ const MONTHS_SHORT = ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'w
const SEARCH_SHELL_SHADOW = '0 5px 10px rgba(0,0,0,0.16), 0 14px 22px rgba(0,0,0,0.24), 0 22px 34px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04)';
const DEFAULT_HORIZON_DAYS = 7;
const PANTRY_CALENDAR_DAY_ATTR = 'data-pantry-calendar-day';
const SHORTFALL_ACCENT = '#CB4A48';
const SHORTFALL_ACCENT = 'rgb(var(--danger-rgb))';
const PANTRY_CALENDAR_THEME = {
bg: '#272622',
border: '#34312c',
text: '#d7d2c8',
dimText: '#888278',
bg: 'rgb(var(--app-bg-rgb))',
border: 'rgb(var(--card-raised-rgb))',
text: 'rgb(var(--text-body-soft-rgb))',
dimText: 'rgb(var(--text-faint-rgb))',
dimOpacity: 0.58,
dimmedBg: 'transparent',
dimmedBorder: 'transparent',
dot: '#7d7a74',
selectedBg: '#393937',
selectedBorder: '#787876',
selectedText: '#f2efe8',
selectedDot: '#f2efe8',
dot: 'rgb(var(--text-faint-rgb))',
selectedBg: 'rgb(var(--card-rgb))',
selectedBorder: 'rgb(var(--border-input-rgb))',
selectedText: 'rgb(var(--text-emphasis-rgb))',
selectedDot: 'rgb(var(--text-emphasis-rgb))',
};
/* ── state ── */
@@ -162,7 +162,7 @@ function photoStripMedia(image, icon, accentBg) {
<img src="${esc(image)}" alt="" class="absolute inset-0 w-full h-full object-cover">
</div>`;
}
return `<div class="w-[4.5rem] shrink-0 flex items-center justify-center self-stretch" style="background:#333331;">
return `<div class="w-[4.5rem] shrink-0 flex items-center justify-center self-stretch" style="background:rgb(var(--card-raised-rgb));">
<i class="fas ${icon} text-[22px]" style="color:rgba(255,255,255,0.10);"></i>
</div>`;
}
@@ -171,32 +171,32 @@ function photoStripMedia(image, icon, accentBg) {
export function getPantryHTML() {
return `
<div id="pantry-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden z-10" style="background:#2d2e2b !important;">
<div id="pantry-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden z-10" style="background:rgb(var(--app-bg-rgb)) !important;">
<!-- ── floating top bar ── -->
<div id="pantry-topbar-outer" class="pointer-events-none absolute inset-x-0 top-0 z-[12] px-4 pt-4 pb-4" style="background:#2d2e2b !important; border:none !important;">
<div id="pantry-topbar-outer" class="pointer-events-none absolute inset-x-0 top-0 z-[12] px-4 pt-4 pb-4" style="background:rgb(var(--app-bg-rgb)) !important; border:none !important;">
<div class="pointer-events-auto relative z-[1] w-full">
<div id="pantry-topbar" class="relative min-h-12">
<div id="pantry-default-row" class="flex min-h-12 items-center gap-2 transition-all duration-200" style="opacity:1; transform:translateY(0) scale(1);">
<h1 class="flex-1 min-w-0 truncate" style="margin:0;padding:0;color:#f2efe8;font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Zapasy</h1>
<h1 class="flex-1 min-w-0 truncate" style="margin:0;padding:0;color:rgb(var(--text-emphasis-rgb));font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Zapasy</h1>
<div id="pantry-horizon-wrap" class="relative shrink">
<button type="button" id="pantry-horizon-compact" class="min-w-0 max-w-[12rem] h-10 rounded-full flex items-center gap-1.5 px-2.5 transition-all" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important;">
<span id="pantry-horizon-compact-label" class="min-w-0 flex-1 text-left text-[13px] font-normal truncate" style="color:#ddd6ca;"></span>
<i id="pantry-horizon-chevron" class="fas fa-chevron-down text-[10px] shrink-0 transition-transform duration-200" style="color:#9b978f;"></i>
<button type="button" id="pantry-horizon-compact" class="min-w-0 max-w-[12rem] h-10 rounded-full flex items-center gap-1.5 px-2.5 transition-all" style="background:rgb(var(--card-rgb)) !important; border:1px solid rgb(var(--border-card-rgb)) !important; box-shadow:${SEARCH_SHELL_SHADOW} !important;">
<span id="pantry-horizon-compact-label" class="min-w-0 flex-1 text-left text-[13px] font-normal truncate" style="color:rgb(var(--text-body-rgb));"></span>
<i id="pantry-horizon-chevron" class="fas fa-chevron-down text-[10px] shrink-0 transition-transform duration-200" style="color:rgb(var(--text-dim-rgb));"></i>
</button>
</div>
<div id="pantry-filter-wrap" class="relative shrink-0">
<button type="button" id="pantry-filter-toggle" class="relative w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:#393937; border:1px solid #41423f; box-shadow:${SEARCH_SHELL_SHADOW}; color:#ddd6ca;">
<button type="button" id="pantry-filter-toggle" class="relative w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:rgb(var(--card-rgb)); border:1px solid rgb(var(--border-card-rgb)); box-shadow:${SEARCH_SHELL_SHADOW}; color:rgb(var(--text-body-rgb));">
<i class="fas fa-sliders-h text-[12px]"></i>
<span id="pantry-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:#23221e; border:1px solid #787876; color:#f2efe8;"></span>
<span id="pantry-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); color:rgb(var(--text-emphasis-rgb));"></span>
</button>
<div id="pantry-filter-popover" class="absolute right-0 top-full mt-2 w-[19rem] max-w-[calc(100vw-2rem)] rounded-[1.35rem] px-3 py-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-6px) scale(0.98);">
<div id="pantry-filter-popover" class="absolute right-0 top-full mt-2 w-[19rem] max-w-[calc(100vw-2rem)] rounded-[1.35rem] px-3 py-3 transition-all duration-200 pointer-events-none" style="background:rgb(var(--sunken-rgb)) !important; border:1px solid rgb(var(--border-input-rgb)) !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-6px) scale(0.98);">
<div class="flex items-center justify-between gap-3 px-0.5 pb-3">
<p class="text-[11px] font-semibold leading-none" style="color:#f2efe8;">Filtry</p>
<button type="button" id="pantry-filter-clear" class="h-8 px-2 rounded-full text-[11px] font-semibold transition-colors" style="background:transparent; border:none; color:#b5afa5;">
<p class="text-[11px] font-semibold leading-none" style="color:rgb(var(--text-emphasis-rgb));">Filtry</p>
<button type="button" id="pantry-filter-clear" class="h-8 px-2 rounded-full text-[11px] font-semibold transition-colors" style="background:transparent; border:none; color:rgb(var(--text-muted-rgb));">
Wyczyść
</button>
</div>
@@ -204,38 +204,38 @@ export function getPantryHTML() {
</div>
</div>
<button type="button" id="pantry-search-toggle" class="w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; color:#ddd6ca;">
<button type="button" id="pantry-search-toggle" class="w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:rgb(var(--card-rgb)) !important; border:1px solid rgb(var(--border-card-rgb)) !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; color:rgb(var(--text-body-rgb));">
<i class="fas fa-search text-[13px]"></i>
</button>
</div>
<div id="pantry-calendar-popover" class="absolute left-0 right-0 top-full mt-2 rounded-[1.35rem] px-3 py-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-6px) scale(0.98);">
<div id="pantry-calendar-popover" class="absolute left-0 right-0 top-full mt-2 rounded-[1.35rem] px-3 py-3 transition-all duration-200 pointer-events-none" style="background:rgb(var(--sunken-rgb)) !important; border:1px solid rgb(var(--border-input-rgb)) !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-6px) scale(0.98);">
${createCalendarTopbarHTML({
prevId: 'pantry-cal-prev',
todayId: 'pantry-cal-today',
nextId: 'pantry-cal-next',
wrapperClass: 'pb-3 flex items-center justify-end gap-3',
controlsStyle: `background:${PANTRY_CALENDAR_THEME.bg};border-color:${PANTRY_CALENDAR_THEME.border};`,
navButtonClass: 'shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[#d7d2c8] transition-colors',
todayButtonActiveClass: 'h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center rounded-full bg-transparent px-1.5 text-[10px] font-semibold leading-none tabular-nums text-[#d7d2c8] active:bg-transparent whitespace-nowrap',
todayButtonDimClass: 'h-full shrink-0 inline-flex items-center justify-center rounded-full px-2 text-[10px] font-semibold leading-none text-[#7d7a74] cursor-default',
navButtonClass: 'shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[rgb(var(--text-body-soft-rgb))] transition-colors',
todayButtonActiveClass: 'h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center rounded-full bg-transparent px-1.5 text-[10px] font-semibold leading-none tabular-nums text-[rgb(var(--text-body-soft-rgb))] active:bg-transparent whitespace-nowrap',
todayButtonDimClass: 'h-full shrink-0 inline-flex items-center justify-center rounded-full px-2 text-[10px] font-semibold leading-none text-[rgb(var(--text-faint-rgb))] cursor-default',
})}
${createCalendarWeekdayHeaderHTML(undefined, {
wrapperClass: 'grid grid-cols-7 gap-1.5 text-center text-[8px] font-medium text-[#9b978f] uppercase tracking-wide mb-1 leading-none',
wrapperClass: 'grid grid-cols-7 gap-1.5 text-center text-[8px] font-medium text-[rgb(var(--text-dim-rgb))] uppercase tracking-wide mb-1 leading-none',
})}
<div id="pantry-calendar-grid" class="grid grid-cols-7 gap-1.5"></div>
<div class="mt-3 pt-3 border-t" style="border-color:#444442;">
<p id="pantry-cal-selection" class="min-w-0 text-[11px] leading-snug" style="color:#9b978f;"></p>
<div class="mt-3 pt-3 border-t" style="border-color:rgb(var(--card-strong-rgb));">
<p id="pantry-cal-selection" class="min-w-0 text-[11px] leading-snug" style="color:rgb(var(--text-dim-rgb));"></p>
</div>
</div>
<div id="pantry-search-shell" class="absolute inset-0 flex items-center gap-2 rounded-full px-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-2px) scale(0.98);">
<i class="fas fa-search text-[13px] shrink-0" style="color:#9b978f;"></i>
<div id="pantry-search-shell" class="absolute inset-0 flex items-center gap-2 rounded-full px-3 transition-all duration-200 pointer-events-none" style="background:rgb(var(--sunken-rgb)) !important; border:1px solid rgb(var(--border-input-rgb)) !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-2px) scale(0.98);">
<i class="fas fa-search text-[13px] shrink-0" style="color:rgb(var(--text-dim-rgb));"></i>
<input type="search" id="pantry-search" autocomplete="off" placeholder="Szukaj w spiżarni…"
class="flex-1 min-w-0 h-full bg-transparent outline-none text-[15px] leading-none py-0" style="background:transparent !important; border:none !important; box-shadow:none !important; color:#ddd6ca; margin:0;">
<button type="button" id="pantry-search-close" class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center transition-colors" style="background:#2f2f2d; border:none; color:#9b978f;">
class="flex-1 min-w-0 h-full bg-transparent outline-none text-[15px] leading-none py-0" style="background:transparent !important; border:none !important; box-shadow:none !important; color:rgb(var(--text-body-rgb)); margin:0;">
<button type="button" id="pantry-search-close" class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center transition-colors" style="background:rgb(var(--card-soft-rgb)); border:none; color:rgb(var(--text-dim-rgb));">
<i class="fas fa-xmark text-[13px]"></i>
</button>
</div>
@@ -244,7 +244,7 @@ export function getPantryHTML() {
</div>
<!-- ── scrollable content ── -->
<div id="pantry-scroll" class="flex-1 overflow-y-auto no-scrollbar px-4 pt-[5.35rem] pb-24" style="background:#2d2e2b !important;">
<div id="pantry-scroll" class="flex-1 overflow-y-auto no-scrollbar px-4 pt-[5.35rem] pb-24" style="background:rgb(var(--app-bg-rgb)) !important;">
<div id="pantry-board"></div>
</div>
</div>
@@ -284,8 +284,8 @@ function syncHorizonUI() {
}
if (compactPill) {
compactPill.style.setProperty('background', showCalendar ? '#23221e' : '#393937', 'important');
compactPill.style.setProperty('border-color', showCalendar ? '#787876' : '#41423f', 'important');
compactPill.style.setProperty('background', showCalendar ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-rgb))', 'important');
compactPill.style.setProperty('border-color', showCalendar ? 'rgb(var(--border-input-rgb))' : 'rgb(var(--border-card-rgb))', 'important');
}
if (popover) {
@@ -304,9 +304,9 @@ function syncHorizonUI() {
}
if (filterToggle) {
const isActive = showFilter || hasActivePantryFilters();
filterToggle.style.setProperty('background', isActive ? '#23221e' : '#393937', 'important');
filterToggle.style.setProperty('border-color', isActive ? '#787876' : '#41423f', 'important');
filterToggle.style.setProperty('color', isActive ? '#f2efe8' : '#ddd6ca', 'important');
filterToggle.style.setProperty('background', isActive ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-rgb))', 'important');
filterToggle.style.setProperty('border-color', isActive ? 'rgb(var(--border-input-rgb))' : 'rgb(var(--border-card-rgb))', 'important');
filterToggle.style.setProperty('color', isActive ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-rgb))', 'important');
}
if (filterCount) {
filterCount.textContent = String(activeFilterCount);
@@ -400,11 +400,11 @@ function renderFilterPopover() {
body.innerHTML = `
<section>
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:#b5afa5;">Kategorie</p>
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:rgb(var(--text-muted-rgb));">Kategorie</p>
<div class="flex flex-wrap gap-2">${categoryChips}</div>
</section>
<section>
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:#b5afa5;">Sekcje</p>
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:rgb(var(--text-muted-rgb));">Sekcje</p>
<div class="flex flex-wrap gap-2">${sectionChips}</div>
</section>
`;
@@ -554,24 +554,24 @@ function classifyIngredients(searchQuery) {
function tileIconHtml(item) {
if (item.image) {
return `<div class="w-6 h-6 rounded-md shrink-0 overflow-hidden" style="background:#2f2f2d;"><img src="${esc(item.image)}" alt="" class="w-full h-full object-contain" style="padding:1px;"></div>`;
return `<div class="w-6 h-6 rounded-md shrink-0 overflow-hidden" style="background:rgb(var(--card-soft-rgb));"><img src="${esc(item.image)}" alt="" class="w-full h-full object-contain" style="padding:1px;"></div>`;
}
return `<div class="w-6 h-6 rounded-md flex items-center justify-center shrink-0" style="background:#2f2f2d;"><i class="fas ${item.icon} text-[11px]" style="color:#7d7a74;"></i></div>`;
return `<div class="w-6 h-6 rounded-md flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${item.icon} text-[11px]" style="color:rgb(var(--text-faint-rgb));"></i></div>`;
}
function shortfallTileHtml(item) {
const clamp = item.name.length > 25 ? ' min-w-0' : '';
return `
<button type="button" class="pv2-tile text-left rounded-2xl flex flex-col gap-2 px-2.5 py-2${clamp} transition-all active:scale-[0.98]" style="flex:1 0 auto; min-width:6rem; max-width:100%; background:#393937; border:none; box-shadow:0 2px 8px rgba(0,0,0,0.28);" data-id="${esc(item.ingredientId)}">
<button type="button" class="pv2-tile text-left rounded-2xl flex flex-col gap-2 px-2.5 py-2${clamp} transition-all active:scale-[0.98]" style="flex:1 0 auto; min-width:6rem; max-width:100%; background:rgb(var(--card-rgb)); border:none; box-shadow:0 2px 8px rgba(0,0,0,0.28);" data-id="${esc(item.ingredientId)}">
<div class="flex items-center gap-1.5 min-w-0">
${tileIconHtml(item)}
<p class="text-[11px] font-normal leading-tight truncate min-w-0" style="color:#ddd6ca;">${esc(item.name)}</p>
<p class="text-[11px] font-normal leading-tight truncate min-w-0" style="color:rgb(var(--text-body-rgb));">${esc(item.name)}</p>
</div>
<div class="w-full flex items-center gap-2">
<div class="flex-1 h-1 rounded-full overflow-hidden" style="background:#2a2a28;">
<div class="flex-1 h-1 rounded-full overflow-hidden" style="background:rgb(var(--app-bg-rgb));">
<div class="h-full rounded-full" style="width:${item.fillPct}%; background:${SHORTFALL_ACCENT};"></div>
</div>
<span class="shrink-0 text-[10px] font-semibold tabular-nums" style="color:#ddd6ca;">${esc(formatQty(item.pantryQty))}<span class="font-medium" style="color:#9b978f;">/${esc(formatQty(item.needed))} ${esc(unitLabel(item.unit))}</span></span>
<span class="shrink-0 text-[10px] font-semibold tabular-nums" style="color:rgb(var(--text-body-rgb));">${esc(formatQty(item.pantryQty))}<span class="font-medium" style="color:rgb(var(--text-dim-rgb));">/${esc(formatQty(item.needed))} ${esc(unitLabel(item.unit))}</span></span>
</div>
</button>`;
}
@@ -579,16 +579,16 @@ function shortfallTileHtml(item) {
function sufficientTileHtml(item) {
const clamp = item.name.length > 25 ? ' min-w-0' : '';
return `
<button type="button" class="pv2-tile text-left rounded-2xl flex flex-col gap-2 px-2.5 py-2${clamp} transition-all active:scale-[0.98]" style="flex:1 0 auto; min-width:6rem; max-width:100%; background:#393937; border:none; box-shadow:0 2px 8px rgba(0,0,0,0.28);" data-id="${esc(item.ingredientId)}">
<button type="button" class="pv2-tile text-left rounded-2xl flex flex-col gap-2 px-2.5 py-2${clamp} transition-all active:scale-[0.98]" style="flex:1 0 auto; min-width:6rem; max-width:100%; background:rgb(var(--card-rgb)); border:none; box-shadow:0 2px 8px rgba(0,0,0,0.28);" data-id="${esc(item.ingredientId)}">
<div class="flex items-center gap-1.5 min-w-0">
${tileIconHtml(item)}
<p class="text-[11px] font-normal leading-tight truncate min-w-0" style="color:#ddd6ca;">${esc(item.name)}</p>
<p class="text-[11px] font-normal leading-tight truncate min-w-0" style="color:rgb(var(--text-body-rgb));">${esc(item.name)}</p>
</div>
<div class="w-full flex items-center gap-2">
<div class="flex-1 h-1 rounded-full overflow-hidden" style="background:#2a2a28;">
<div class="h-full rounded-full" style="width:100%; background:#6ee7b7;"></div>
<div class="flex-1 h-1 rounded-full overflow-hidden" style="background:rgb(var(--app-bg-rgb));">
<div class="h-full rounded-full" style="width:100%; background:rgb(var(--success-rgb));"></div>
</div>
<span class="shrink-0 text-[10px] font-semibold tabular-nums" style="color:#6ee7b7;">${esc(formatQty(item.pantryQty))}<span class="font-medium" style="color:#9b978f;">/${esc(formatQty(item.needed))} ${esc(unitLabel(item.unit))}</span></span>
<span class="shrink-0 text-[10px] font-semibold tabular-nums" style="color:rgb(var(--success-rgb));">${esc(formatQty(item.pantryQty))}<span class="font-medium" style="color:rgb(var(--text-dim-rgb));">/${esc(formatQty(item.needed))} ${esc(unitLabel(item.unit))}</span></span>
</div>
</button>`;
}
@@ -597,10 +597,10 @@ function notPlannedChipHtml(item) {
const hasStock = item.qty > 0;
const clamp = item.name.length > 25 ? ' min-w-0' : '';
return `
<button type="button" class="pv2-tile text-left rounded-2xl flex items-center gap-1.5 px-2.5 py-2${clamp} transition-all active:scale-[0.98]" style="flex:1 0 auto; min-width:6rem; max-width:100%; background:#393937; border:none; box-shadow:0 2px 8px rgba(0,0,0,0.28);" data-id="${esc(item.ingredientId)}">
<button type="button" class="pv2-tile text-left rounded-2xl flex items-center gap-1.5 px-2.5 py-2${clamp} transition-all active:scale-[0.98]" style="flex:1 0 auto; min-width:6rem; max-width:100%; background:rgb(var(--card-rgb)); border:none; box-shadow:0 2px 8px rgba(0,0,0,0.28);" data-id="${esc(item.ingredientId)}">
${tileIconHtml(item)}
<p class="text-[11px] font-normal leading-tight truncate min-w-0" style="color:${hasStock ? '#b7ada1' : '#9b978f'};">${esc(item.name)}</p>
<span class="text-[10px] font-semibold tabular-nums shrink-0 ml-auto" style="color:${hasStock ? '#9b978f' : '#6d6c67'};">${esc(formatQty(item.qty))} ${esc(unitLabel(item.unit))}</span>
<p class="text-[11px] font-normal leading-tight truncate min-w-0" style="color:${hasStock ? 'rgb(var(--text-muted-rgb))' : 'rgb(var(--text-dim-rgb))'};">${esc(item.name)}</p>
<span class="text-[10px] font-semibold tabular-nums shrink-0 ml-auto" style="color:${hasStock ? 'rgb(var(--text-dim-rgb))' : 'rgb(var(--text-subdued-rgb))'};">${esc(formatQty(item.qty))} ${esc(unitLabel(item.unit))}</span>
</button>`;
}
@@ -610,7 +610,7 @@ function sectionHeaderHtml(icon, iconBg, iconColor, title, titleColor, count) {
return `
<div class="flex items-center gap-2 mb-2.5 px-0.5">
<span class="text-[10px] font-bold uppercase tracking-wider" style="color:${titleColor};">${esc(title)}</span>
<span class="text-[10px]" style="color:#6d6c67;">${count}</span>
<span class="text-[10px]" style="color:rgb(var(--text-subdued-rgb));">${count}</span>
</div>`;
}
@@ -624,7 +624,7 @@ function renderBoard() {
const totalVisible = shortfalls.length + sufficient.length + notPlanned.length;
if (totalVisible === 0 && (q || hasFilters)) {
root.innerHTML = `<p class="text-sm text-center py-10" style="color:#9b978f;">Brak wyników — zmień filtry lub wyszukiwanie.</p>`;
root.innerHTML = `<p class="text-sm text-center py-10" style="color:rgb(var(--text-dim-rgb));">Brak wyników — zmień filtry lub wyszukiwanie.</p>`;
return;
}
@@ -633,7 +633,7 @@ function renderBoard() {
// Section 1: Potrzebne (shortfalls)
if (shortfalls.length > 0) {
html += `<section class="mb-5">`;
html += sectionHeaderHtml('fa-exclamation', '#2f2f2d', SHORTFALL_ACCENT, 'Potrzebne', '#7B756D', shortfalls.length);
html += sectionHeaderHtml('fa-exclamation', 'rgb(var(--card-soft-rgb))', SHORTFALL_ACCENT, 'Potrzebne', 'rgb(var(--text-faint-rgb))', shortfalls.length);
html += `<div class="flex flex-wrap gap-2">`;
html += shortfalls.sort((a, b) => a.name.length - b.name.length).map(shortfallTileHtml).join('');
html += `</div></section>`;
@@ -642,7 +642,7 @@ function renderBoard() {
// Section 2: W spiżarni (sufficient)
if (sufficient.length > 0) {
html += `<section class="mb-5">`;
html += sectionHeaderHtml('fa-check', '#1a2e22', '#6ee7b7', 'W spiżarni', '#7B756D', sufficient.length);
html += sectionHeaderHtml('fa-check', 'rgba(var(--success-rgb), 0.14)', 'rgb(var(--success-rgb))', 'W spiżarni', 'rgb(var(--text-faint-rgb))', sufficient.length);
html += `<div class="flex flex-wrap gap-2">`;
html += sufficient.sort((a, b) => a.name.length - b.name.length).map(sufficientTileHtml).join('');
html += `</div></section>`;
@@ -651,7 +651,7 @@ function renderBoard() {
// Section 3: Poza planem
if (notPlanned.length > 0) {
html += `<section class="mb-5">`;
html += sectionHeaderHtml('fa-minus', '#2a2a28', '#6d6c67', 'Poza planem', '#7B756D', notPlanned.length);
html += sectionHeaderHtml('fa-minus', 'rgb(var(--app-bg-rgb))', 'rgb(var(--text-subdued-rgb))', 'Poza planem', 'rgb(var(--text-faint-rgb))', notPlanned.length);
html += `<div class="flex flex-wrap gap-2">`;
html += notPlanned.map(notPlannedChipHtml).join('');
html += `</div></section>`;
@@ -661,11 +661,11 @@ function renderBoard() {
if (shortfalls.length === 0 && sufficient.length === 0 && !q && !hasFilters) {
html = `
<div class="flex flex-col items-center justify-center py-10 text-center mb-6">
<div class="w-12 h-12 rounded-2xl flex items-center justify-center mb-3" style="background:#393937;">
<i class="fas fa-calendar-xmark text-lg" style="color:#6d6c67;"></i>
<div class="w-12 h-12 rounded-2xl flex items-center justify-center mb-3" style="background:rgb(var(--card-rgb));">
<i class="fas fa-calendar-xmark text-lg" style="color:rgb(var(--text-subdued-rgb));"></i>
</div>
<p class="text-[13px] font-semibold mb-1" style="color:#ddd6ca;">Brak zaplanowanych posiłków</p>
<p class="text-[11px] max-w-[16rem]" style="color:#9b978f;">Zaplanuj posiłki, a spiżarnia pokaże czego potrzebujesz i co masz na stanie.</p>
<p class="text-[13px] font-semibold mb-1" style="color:rgb(var(--text-body-rgb));">Brak zaplanowanych posiłków</p>
<p class="text-[11px] max-w-[16rem]" style="color:rgb(var(--text-dim-rgb));">Zaplanuj posiłki, a spiżarnia pokaże czego potrzebujesz i co masz na stanie.</p>
</div>` + html;
}

View File

@@ -10,15 +10,15 @@ function escapeHtml(s) {
}
const RD_THEME = Object.freeze({
surface: '#393937',
surfaceSoft: '#2f2f2d',
surfaceActive: '#23221e',
border: '#444442',
borderSoft: '#56534f',
borderStrong: '#787876',
textPrimary: '#ddd6ca',
textSecondary: '#d7d2c8',
textMuted: '#9b978f',
surface: 'rgb(var(--card-rgb))',
surfaceSoft: 'rgb(var(--card-soft-rgb))',
surfaceActive: 'rgb(var(--sunken-rgb))',
border: 'rgb(var(--card-strong-rgb))',
borderSoft: 'rgba(var(--border-input-rgb), 0.58)',
borderStrong: 'rgb(var(--border-input-rgb))',
textPrimary: 'rgb(var(--text-body-rgb))',
textSecondary: 'rgb(var(--text-body-soft-rgb))',
textMuted: 'rgb(var(--text-dim-rgb))',
});
function forceBg(bg) {
@@ -31,27 +31,27 @@ function forceBgBorder(bg, border) {
export function getRecipeDetailHTML() {
return `
<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;">
<div id="recipe-detail-view" class="absolute inset-0 bg-[rgb(var(--app-bg-rgb))] z-30 transition-all duration-300 ease-in-out translate-x-full opacity-0 pointer-events-none overflow-hidden" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important;">
<div class="absolute top-0 w-full p-3.5 flex justify-between z-40 mt-3">
<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;">
<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(var(--card-rgb),0.93) !important; backdrop-filter:none !important; box-shadow:0 4px 9px rgba(0,0,0,0.33) !important; color:rgb(var(--text-body-rgb)) !important; transition:box-shadow 180ms ease, background-color 180ms ease, opacity 180ms ease;">
<i class="fas fa-arrow-left text-[13px]"></i>
</button>
<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;">
<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(var(--card-rgb),0.93) !important; backdrop-filter:none !important; box-shadow:0 3px 8px rgba(0,0,0,0.28) !important; color:rgb(var(--text-body-rgb)) !important; transition:box-shadow 180ms ease, background-color 180ms ease, opacity 180ms ease;">
<i class="fas fa-calendar-plus text-[11px]"></i> Zaplanuj
</button>
</div>
<div id="rd-scroll-container" class="absolute inset-0 z-10 overflow-y-auto no-scrollbar" style="overscroll-behavior-y:none;">
<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;">
<div id="rd-hero" class="h-[236px] w-full relative overflow-hidden" style="background:linear-gradient(180deg, rgb(var(--card-raised-rgb)) 0%, rgb(var(--sunken-rgb)) 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>
<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 class="absolute inset-0 pointer-events-none" style="background:linear-gradient(to bottom, rgba(var(--app-bg-rgb),0.1), rgba(var(--app-bg-rgb),0.4), rgba(var(--app-bg-rgb),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:rgb(var(--text-body-rgb));"></span>
</div>
<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 id="rd-content-body" class="bg-[rgb(var(--app-bg-rgb))] rounded-t-3xl -mt-6 relative z-30 pt-6 min-h-screen" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important; box-shadow:0 -8px 20px rgba(0,0,0,0.35) !important;">
<div class="mb-3 px-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:rgb(var(--text-body-rgb));"></h1>
</div>
</div>
@@ -253,17 +253,17 @@ function renderNutritionSummary(recipe) {
? `
<div class="mt-3 flex items-center justify-between gap-3">
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider">Porcje</p>
<p class="pr-1 text-[13px] font-semibold leading-none text-[#d7d2c8] tabular-nums">${currentServings}</p>
<p class="pr-1 text-[13px] font-semibold leading-none text-[rgb(var(--text-body-soft-rgb))] tabular-nums">${currentServings}</p>
</div>`
: `
<div class="mt-3 flex items-center justify-between gap-3">
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider">Porcje</p>
<div class="flex h-[2rem] w-[5.25rem] shrink-0 items-center gap-0.5 rounded-full border px-0.5" style="background:#2f2f2d;border-color:#444442;box-shadow:0 2px 8px rgba(0,0,0,0.25);">
<button type="button" id="rd-serv-minus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[#d7d2c8] transition-colors" aria-label="Zmniejsz liczbę porcji">
<div class="flex h-[2rem] w-[5.25rem] shrink-0 items-center gap-0.5 rounded-full border px-0.5" style="background:rgb(var(--card-soft-rgb));border-color:rgb(var(--card-strong-rgb));box-shadow:0 2px 8px rgba(0,0,0,0.25);">
<button type="button" id="rd-serv-minus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[rgb(var(--text-body-soft-rgb))] transition-colors" aria-label="Zmniejsz liczbę porcji">
<i class="fas fa-minus text-[10px]"></i>
</button>
<span id="rd-servings" class="flex-1 h-full inline-flex items-center justify-center px-0.5 text-[12px] font-semibold leading-none text-[#d7d2c8] tabular-nums">${currentServings}</span>
<button type="button" id="rd-serv-plus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[#d7d2c8] transition-colors" aria-label="Zwiększ liczbę porcji">
<span id="rd-servings" class="flex-1 h-full inline-flex items-center justify-center px-0.5 text-[12px] font-semibold leading-none text-[rgb(var(--text-body-soft-rgb))] tabular-nums">${currentServings}</span>
<button type="button" id="rd-serv-plus" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[rgb(var(--text-body-soft-rgb))] transition-colors" aria-label="Zwiększ liczbę porcji">
<i class="fas fa-plus text-[10px]"></i>
</button>
</div>
@@ -271,23 +271,23 @@ function renderNutritionSummary(recipe) {
return `
<div class="mb-4">
<div class="h-full pb-2 flex flex-col" style="background:#2d2e2b !important; background-image:none !important; box-shadow:none !important;">
<div class="h-full pb-2 flex flex-col" style="background:rgb(var(--app-bg-rgb)) !important; background-image:none !important; box-shadow:none !important;">
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Wartości odżywcze</p>
<div class="flex-1 flex items-center">
<div class="grid grid-cols-4 gap-1.5 w-full">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-gray-100 tabular-nums leading-tight">${total.kcal}</p>
<p class="text-[9px] text-gray-500 font-medium">kcal</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-blue-400 tabular-nums leading-tight">${total.protein}g</p>
<p class="text-[9px] text-gray-500 font-medium">białko</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-amber-400 tabular-nums leading-tight">${total.fat}g</p>
<p class="text-[9px] text-gray-500 font-medium">tłuszcz</p>
</div>
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:#393937;">
<div class="rounded-xl px-2 py-[0.5625rem] text-center" style="background:rgb(var(--card-rgb));">
<p class="text-[15px] font-bold text-orange-400 tabular-nums leading-tight">${total.carbs}g</p>
<p class="text-[9px] text-gray-500 font-medium">węglowodany</p>
</div>
@@ -306,12 +306,12 @@ function renderIngredients(recipe) {
const items = buildVisibleIngredients(recipe);
const rows = items.map((item) => {
const rowClass = 'rd-ing-row rounded-xl px-3 py-3 w-full text-left cursor-pointer transition-colors active:scale-[0.99]';
const rowStyle = 'background:#393937 !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;';
const rowStyle = 'background:rgb(var(--card-rgb)) !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;';
const productBadge = item.productName
? `<div class="flex items-center gap-1 mt-0.5"><span class="text-[10px] text-emerald-400 truncate">${escapeHtml(item.productName)}</span></div>`
: '';
const addedMark = item.added
? '<span class="shrink-0 inline-flex items-center justify-center text-[#8f8b84]" title="Dodany składnik" aria-label="Dodany składnik"><i class="fas fa-plus text-[8px]"></i></span>'
? '<span class="shrink-0 inline-flex items-center justify-center text-[rgb(var(--text-faint-rgb))]" title="Dodany składnik" aria-label="Dodany składnik"><i class="fas fa-plus text-[8px]"></i></span>'
: '';
return `<li>
<button type="button" class="${rowClass}" style="${rowStyle}" data-rd-open-ingredient data-rd-ingredient-id="${escapeHtml(item.ingredientId)}" data-rd-product-id="${escapeHtml(item.productId || '')}">
@@ -364,7 +364,7 @@ function renderIngredients(recipe) {
const scaledAmount = ing.amount * currentServings;
const isExpanded = expandedAlternatives.has(origId);
const rowClass = 'rd-ing-row rounded-xl px-3 py-3';
const rowStyle = 'background:#393937 !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;';
const rowStyle = 'background:rgb(var(--card-rgb)) !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;';
const toggleBtn = hasAlts
? `<button type="button" class="rd-alt-toggle shrink-0 w-5 h-5 flex items-center justify-center transition-colors text-gray-400 hover:text-gray-300" style="background:transparent !important; box-shadow:none !important;" data-original-id="${escapeHtml(origId)}" aria-label="Wybierz zamiennik składnika"><i class="fas fa-shuffle text-[10px]"></i></button>`
@@ -392,14 +392,14 @@ function renderIngredients(recipe) {
const altNutrition = nutritionForAmount(altId, scaledAmount, ing.unit);
const checkbox = `
<span class="ml-auto self-center w-[18px] h-[18px] rounded-full shrink-0 flex items-center justify-center"
style="border:1.5px solid #56534f; background:transparent;">
${isSelected ? '<i class="fas fa-check" style="color:#9b978f; font-size:8px; line-height:1; display:block; transform:translateY(0.5px);"></i>' : ''}
style="border:1.5px solid rgba(var(--border-input-rgb), 0.58); background:transparent;">
${isSelected ? '<i class="fas fa-check" style="color:rgb(var(--text-dim-rgb)); font-size:8px; line-height:1; display:block; transform:translateY(0.5px);"></i>' : ''}
</span>`;
const nutritionLine = altNutrition
? `<div class="text-[10px] text-gray-400 mt-0.5 tabular-nums">${altNutrition.kcal} kcal · ${altNutrition.protein}g B · ${altNutrition.fat}g T · ${altNutrition.carbs}g W</div>`
: '';
return `<button type="button" class="rd-alt-pick w-full text-left p-2.5 rounded-lg transition-all" style="background:#2f2f2d !important; background-image:none !important; border:none !important; box-shadow:none !important;" data-original-id="${escapeHtml(origId)}" data-alt-id="${escapeHtml(altId)}"><div class="flex items-center gap-3"><div class="min-w-0 flex-1"><div class="text-[11px] font-semibold text-gray-900">${escapeHtml(altName)}</div>${nutritionLine}</div>${checkbox}</div></button>`;
return `<button type="button" class="rd-alt-pick w-full text-left p-2.5 rounded-lg transition-all" style="background:rgb(var(--card-soft-rgb)) !important; background-image:none !important; border:none !important; box-shadow:none !important;" data-original-id="${escapeHtml(origId)}" data-alt-id="${escapeHtml(altId)}"><div class="flex items-center gap-3"><div class="min-w-0 flex-1"><div class="text-[11px] font-semibold text-gray-900">${escapeHtml(altName)}</div>${nutritionLine}</div>${checkbox}</div></button>`;
});
altListHtml = `
<div class="mt-2 ml-1 space-y-1 rd-alt-options" data-original-id="${escapeHtml(origId)}">
@@ -507,7 +507,7 @@ export function setupRecipeDetail() {
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})`;
button.style.background = `rgba(var(--card-rgb),${backgroundAlpha})`;
});
}

View File

@@ -113,31 +113,31 @@ function renderGrid() {
export function getRecipeListHTML() {
return `
<div id="main-view" class="flex flex-col h-full absolute inset-0 bg-[#2d2e2b] z-10" style="background:#2d2e2b !important;">
<div id="recipe-top-bar" class="pointer-events-none absolute inset-x-0 top-0 z-[12] px-4 pt-4 pb-4" style="background:#2d2e2b !important; border:none !important;">
<div id="main-view" class="flex flex-col h-full absolute inset-0 bg-[rgb(var(--app-bg-rgb))] z-10" style="background:rgb(var(--app-bg-rgb)) !important;">
<div id="recipe-top-bar" class="pointer-events-none absolute inset-x-0 top-0 z-[12] px-4 pt-4 pb-4" style="background:rgb(var(--app-bg-rgb)) !important; border:none !important;">
<div class="pointer-events-auto relative z-[1] w-full">
<div id="recipe-topbar" class="relative min-h-12">
<div id="recipe-default-row" class="flex min-h-12 items-center gap-2 transition-all duration-200" style="opacity:1; transform:translateY(0) scale(1);">
<h1 class="min-w-0 flex-1 truncate" style="margin:0;padding:0;color:#f2efe8;font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Katalog przepisów</h1>
<h1 class="min-w-0 flex-1 truncate" style="margin:0;padding:0;color:rgb(var(--text-emphasis-rgb));font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Katalog przepisów</h1>
<div id="recipe-filter-wrap" class="relative shrink-0">
<button type="button" id="recipe-filter-btn" class="relative w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:#393937; border:1px solid #41423f; box-shadow:${SEARCH_SHELL_SHADOW}; color:#ddd6ca;" aria-label="Otwórz filtry">
<button type="button" id="recipe-filter-btn" class="relative w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:rgb(var(--card-rgb)); border:1px solid rgb(var(--border-card-rgb)); box-shadow:${SEARCH_SHELL_SHADOW}; color:rgb(var(--text-body-rgb));" aria-label="Otwórz filtry">
<i class="fas fa-sliders-h text-[12px]" aria-hidden="true"></i>
<span id="recipe-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:#23221e; border:1px solid #787876; color:#f2efe8;"></span>
<span id="recipe-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); color:rgb(var(--text-emphasis-rgb));"></span>
</button>
</div>
<button type="button" id="recipe-search-toggle" class="w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; color:#ddd6ca;" aria-label="Szukaj">
<button type="button" id="recipe-search-toggle" class="w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:rgb(var(--card-rgb)) !important; border:1px solid rgb(var(--border-card-rgb)) !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; color:rgb(var(--text-body-rgb));" aria-label="Szukaj">
<i class="fas fa-search text-[13px]" aria-hidden="true"></i>
</button>
</div>
<div id="recipe-search-shell" class="absolute inset-0 flex items-center gap-2 rounded-full px-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-2px) scale(0.98);">
<i class="fas fa-search text-[13px] shrink-0" style="color:#9b978f;"></i>
<div id="recipe-search-shell" class="absolute inset-0 flex items-center gap-2 rounded-full px-3 transition-all duration-200 pointer-events-none" style="background:rgb(var(--sunken-rgb)) !important; border:1px solid rgb(var(--border-input-rgb)) !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-2px) scale(0.98);">
<i class="fas fa-search text-[13px] shrink-0" style="color:rgb(var(--text-dim-rgb));"></i>
<input type="search" id="recipe-search-input" autocomplete="off" placeholder="Szukaj przepisów…"
class="flex-1 min-w-0 h-full bg-transparent outline-none text-[15px] leading-none py-0" style="background:transparent !important; border:none !important; box-shadow:none !important; color:#ddd6ca; margin:0;">
<button type="button" id="recipe-search-close" class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center transition-colors" style="background:#2f2f2d; border:none; color:#9b978f;">
class="flex-1 min-w-0 h-full bg-transparent outline-none text-[15px] leading-none py-0" style="background:transparent !important; border:none !important; box-shadow:none !important; color:rgb(var(--text-body-rgb)); margin:0;">
<button type="button" id="recipe-search-close" class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center transition-colors" style="background:rgb(var(--card-soft-rgb)); border:none; color:rgb(var(--text-dim-rgb));">
<i class="fas fa-xmark text-[13px]"></i>
</button>
</div>
@@ -149,8 +149,8 @@ export function getRecipeListHTML() {
scrollId: 'recipe-scroll',
gridId: 'recipe-grid',
emptyStateId: 'recipe-empty-state',
scrollClassName: 'relative flex-1 overflow-y-auto px-4 pt-[5.35rem] pb-24 bg-[#2d2e2b]',
gridClassName: 'grid grid-cols-3 gap-2 bg-[#2d2e2b]',
scrollClassName: 'relative flex-1 overflow-y-auto px-4 pt-[5.35rem] pb-24 bg-[rgb(var(--app-bg-rgb))]',
gridClassName: 'grid grid-cols-3 gap-2 bg-[rgb(var(--app-bg-rgb))]',
emptyTitle: 'Brak wyników',
emptyMessage: 'Zmień kryteria wyszukiwania lub filtry',
})}

View File

@@ -54,7 +54,7 @@ const DAY_NAMES_SHORT = ['nd.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'];
const WEEKDAY_SHORT = ['pn', 'wt', 'śr', 'cz', 'pt', 'sb', 'nd'];
const MONTHS_LONG = ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'];
const MONTHS_SHORT = ['sty','lut','mar','kwi','maj','cze','lip','sie','wrz','paź','lis','gru'];
const CALENDAR_DIM_TEXT = '#888278';
const CALENDAR_DIM_TEXT = 'rgb(var(--text-faint-rgb))';
const CALENDAR_DIM_OPACITY = '0.58';
/* ── module state ── */
@@ -118,36 +118,36 @@ export function getShoppingListHTML() {
.join('');
return `
<div id="shopping-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden z-10" style="background:#2d2e2b !important;">
<div id="shopping-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden z-10" style="background:rgb(var(--app-bg-rgb)) !important;">
<!-- ── header ── -->
<div class="shrink-0 px-4 pt-5 pb-0">
<!-- title row + pill (position:relative anchors the popup) -->
<div class="flex items-center gap-2 mb-3" style="position:relative;">
<h1 class="flex-1 text-[18px] font-bold" style="color:#f2efe8;">Lista zakupów</h1>
<button type="button" id="sl-range-pill" class="min-w-0 max-w-[10rem] h-10 rounded-full flex items-center gap-1.5 px-2.5 transition-all shrink" style="background:#393937; border:1px solid #41423f; box-shadow:${SL_SHADOW};">
<span id="sl-range-label" class="min-w-0 flex-1 text-left text-[13px] font-normal truncate" style="color:#ddd6ca;"></span>
<i id="sl-range-chevron" class="fas fa-chevron-down text-[10px] shrink-0 transition-transform duration-200" style="color:#9b978f;"></i>
<h1 class="flex-1 text-[18px] font-bold" style="color:rgb(var(--text-emphasis-rgb));">Lista zakupów</h1>
<button type="button" id="sl-range-pill" class="min-w-0 max-w-[10rem] h-10 rounded-full flex items-center gap-1.5 px-2.5 transition-all shrink" style="background:rgb(var(--card-rgb)); border:1px solid rgb(var(--border-card-rgb)); box-shadow:${SL_SHADOW};">
<span id="sl-range-label" class="min-w-0 flex-1 text-left text-[13px] font-normal truncate" style="color:rgb(var(--text-body-rgb));"></span>
<i id="sl-range-chevron" class="fas fa-chevron-down text-[10px] shrink-0 transition-transform duration-200" style="color:rgb(var(--text-dim-rgb));"></i>
</button>
<!-- popup calendar (absolute, overlays content below) -->
<div id="sl-calendar-popup" style="position:absolute; top:calc(100% + 0.5rem); left:0; right:0; z-index:50; pointer-events:none; opacity:0; transform:translateY(-6px) scale(0.98); transition: opacity 0.2s ease, transform 0.2s ease;">
<div class="rounded-[1.35rem] px-3 py-3" style="background:#23221e; border:1px solid #787876; box-shadow:${SL_SHADOW};">
<div class="rounded-[1.35rem] px-3 py-3" style="background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); box-shadow:${SL_SHADOW};">
<!-- month nav topbar -->
<div class="pb-3 flex items-center justify-end gap-3">
<div class="flex h-[2.05rem] min-w-0 max-w-[min(100%,20rem)] items-center gap-px rounded-full border px-0.5" style="background:#272622; border-color:#34312c;">
<button type="button" id="sl-cal-prev" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full bg-transparent transition-colors" style="color:#d7d2c8;">
<div class="flex h-[2.05rem] min-w-0 max-w-[min(100%,20rem)] items-center gap-px rounded-full border px-0.5" style="background:rgb(var(--app-bg-rgb)); border-color:rgb(var(--card-raised-rgb));">
<button type="button" id="sl-cal-prev" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full bg-transparent transition-colors" style="color:rgb(var(--text-body-soft-rgb));">
<i class="fas fa-chevron-left text-[10px]"></i>
</button>
<span id="sl-cal-month-label" class="h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center px-1.5 text-[10px] font-semibold leading-none tabular-nums whitespace-nowrap" style="color:#d7d2c8;"></span>
<button type="button" id="sl-cal-next" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full bg-transparent transition-colors" style="color:#d7d2c8;">
<span id="sl-cal-month-label" class="h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center px-1.5 text-[10px] font-semibold leading-none tabular-nums whitespace-nowrap" style="color:rgb(var(--text-body-soft-rgb));"></span>
<button type="button" id="sl-cal-next" class="shrink-0 w-7 h-full flex items-center justify-center rounded-full bg-transparent transition-colors" style="color:rgb(var(--text-body-soft-rgb));">
<i class="fas fa-chevron-right text-[10px]"></i>
</button>
</div>
</div>
<!-- weekday header -->
<div class="grid grid-cols-7 gap-1.5 text-center text-[8px] font-medium uppercase tracking-wide mb-1 leading-none" style="color:#9b978f;">${weekdayHeader}</div>
<div class="grid grid-cols-7 gap-1.5 text-center text-[8px] font-medium uppercase tracking-wide mb-1 leading-none" style="color:rgb(var(--text-dim-rgb));">${weekdayHeader}</div>
<!-- day grid -->
<div id="sl-cal-grid" class="grid grid-cols-7 gap-1.5"></div>
</div>
@@ -156,12 +156,12 @@ export function getShoppingListHTML() {
<!-- progress -->
<div id="sl-progress-wrap" class="mb-3">
<span class="text-[12px] font-semibold block mb-1.5" style="color:#9b978f;">Kupione <span id="sl-progress-text">0/0</span></span>
<span class="text-[12px] font-semibold block mb-1.5" style="color:rgb(var(--text-dim-rgb));">Kupione</span>
<div class="flex items-center gap-2">
<div class="flex-1 h-1.5 rounded-full overflow-hidden" style="background:#393937;">
<div class="flex-1 h-1.5 rounded-full overflow-hidden" style="background:rgb(var(--card-rgb));">
<div id="sl-progress-bar" class="h-full rounded-full transition-all duration-300" style="background:rgb(var(--success-rgb)); width:0%;"></div>
</div>
<button type="button" id="sl-clear-session" class="flex items-center justify-center flex-shrink-0" style="color:#6b6965;" aria-label="Wyczyść kupione">
<button type="button" id="sl-clear-session" class="flex items-center justify-center flex-shrink-0" style="color:rgb(var(--text-subdued-rgb));" aria-label="Wyczyść kupione">
<i class="fas fa-trash-can" style="font-size:14px;" aria-hidden="true"></i>
</button>
</div>
@@ -169,12 +169,12 @@ export function getShoppingListHTML() {
</div>
<!-- ── scrollable list ── -->
<div id="sl-scroll" class="flex-1 overflow-y-auto no-scrollbar px-4 pt-3 pb-24" style="background:#2d2e2b !important;">
<div id="sl-scroll" class="flex-1 overflow-y-auto no-scrollbar px-4 pt-3 pb-24" style="background:rgb(var(--app-bg-rgb)) !important;">
<div id="sl-board"></div>
<!-- bought section -->
<div id="sl-bought-section" class="mt-3 hidden">
<button type="button" id="sl-bought-toggle" class="flex items-center gap-2 w-full py-2 px-1 text-left" style="color:#9b978f;">
<button type="button" id="sl-bought-toggle" class="flex items-center gap-2 w-full py-2 px-1 text-left" style="color:rgb(var(--text-dim-rgb));">
<i id="sl-bought-chevron" class="fas fa-chevron-right text-[10px] transition-transform duration-200"></i>
<span class="text-[11px] font-bold uppercase tracking-wider">Kupione (<span id="sl-bought-count">0</span>)</span>
</button>
@@ -222,15 +222,15 @@ function renderCalendarGrid(previewDays = null) {
let bg, borderColor, color, opacity, borderClass;
if (isSel) {
bg = '#393937'; borderColor = '#787876'; color = '#f2efe8'; opacity = '1'; borderClass = 'border';
bg = 'rgb(var(--card-rgb))'; borderColor = 'rgb(var(--border-input-rgb))'; color = 'rgb(var(--text-emphasis-rgb))'; opacity = '1'; borderClass = 'border';
} else if (!inMonth) {
bg = 'transparent'; borderColor = 'transparent'; color = CALENDAR_DIM_TEXT; opacity = CALENDAR_DIM_OPACITY; borderClass = 'border-0';
} else {
bg = '#272622'; borderColor = '#34312c'; color = '#d7d2c8'; opacity = '1'; borderClass = 'border';
bg = 'rgb(var(--app-bg-rgb))'; borderColor = 'rgb(var(--card-raised-rgb))'; color = 'rgb(var(--text-body-soft-rgb))'; opacity = '1'; borderClass = 'border';
}
const dot = isToday && !isSel
? `<span class="absolute left-1/2 -translate-x-1/2 w-1 h-1 rounded-full opacity-75" style="bottom:0.24rem; background:#7d7a74;"></span>`
? `<span class="absolute left-1/2 -translate-x-1/2 w-1 h-1 rounded-full opacity-75" style="bottom:0.24rem; background:rgb(var(--text-faint-rgb));"></span>`
: '';
return `<button type="button" class="sl-cal-day mx-auto flex h-[2.05rem] w-full min-w-0 max-w-full items-center justify-center rounded-full ${borderClass} text-xs font-medium leading-tight overflow-hidden"
@@ -315,8 +315,8 @@ function openCalendar() {
}
if (chevron) chevron.style.transform = 'rotate(180deg)';
if (pill) {
pill.style.background = '#23221e';
pill.style.borderColor = '#787876';
pill.style.background = 'rgb(var(--sunken-rgb))';
pill.style.borderColor = 'rgb(var(--border-input-rgb))';
}
renderCalendarGrid();
}
@@ -333,8 +333,8 @@ function closeCalendar() {
}
if (chevron) chevron.style.transform = '';
if (pill) {
pill.style.background = '#393937';
pill.style.borderColor = '#41423f';
pill.style.background = 'rgb(var(--card-rgb))';
pill.style.borderColor = 'rgb(var(--border-card-rgb))';
}
}
@@ -344,44 +344,48 @@ function toggleCalendar() {
/* ══════════════════════ ITEM ROWS ══════════════════════ */
function activeItemHtml(item, isPartial) {
function activeItemHtml(item) {
const def = INGREDIENTS[item.ingredientId];
const icon = CATEGORY_ICONS[def?.category] || 'fa-jar';
const image = def?.image;
const mediaFit = image?.endsWith('.svg') ? 'object-contain' : 'object-cover';
const mediaHtml = image
? `<img src="${esc(image)}" alt="" class="w-8 h-8 rounded-lg ${mediaFit} shrink-0">`
: `<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0" style="background:#2f2f2d;"><i class="fas ${icon} text-xs" style="color:#8f8b84;"></i></div>`;
: `<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${icon} text-xs" style="color:rgb(var(--text-faint-rgb));"></i></div>`;
const isExpanded = expandedIngredientId === item.ingredientId;
const step = stepForUnit(item.unit);
const stepAmt = isExpanded ? expandedAmount : Math.max(step, Math.round(item.shortfall / step) * step);
return `
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:#4a7c59; box-shadow:0 2px 8px rgba(0,0,0,0.28);">
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:rgb(var(--success-rgb)); box-shadow:0 2px 8px rgba(0,0,0,0.28);">
<div class="sl-swipe-bg-buy absolute inset-0 flex items-center pr-5 justify-end">
<i class="fas fa-check text-white text-lg"></i>
</div>
<div class="sl-swipe-inner rounded-xl" style="background:#393937; position:relative;"
<div class="sl-swipe-inner rounded-xl" style="background:rgb(var(--card-rgb)); position:relative;"
data-id="${esc(item.ingredientId)}" data-unit="${esc(item.unit)}" data-shortfall="${item.shortfall}">
<div class="sl-item-main flex items-center gap-3 py-1.5 px-3 cursor-pointer select-none">
${mediaHtml}
<div class="flex-1 min-w-0">
<span class="block text-[13px] font-medium leading-tight truncate" style="color:#ddd6ca;">${esc(item.name)}</span>
${isPartial ? `<span class="text-[10px]" style="color:rgb(var(--accent-rgb));">część już w spiżarni</span>` : ''}
<span class="block text-[13px] font-medium leading-tight truncate" style="color:rgb(var(--text-body-rgb));">${esc(item.name)}</span>
</div>
<span class="text-[13px] font-semibold tabular-nums shrink-0" style="color:#b7ada1;">${esc(formatQty(item.shortfall))} ${esc(unitLabel(item.unit))}</span>
<i class="fas fa-chevron-${isExpanded ? 'up' : 'down'} text-[9px] shrink-0" style="color:#6d6c67;"></i>
<span class="text-[13px] font-semibold tabular-nums shrink-0" style="color:rgb(var(--text-muted-rgb));">${esc(formatQty(item.shortfall))} ${esc(unitLabel(item.unit))}</span>
<i class="fas fa-chevron-${isExpanded ? 'up' : 'down'} text-[9px] shrink-0" style="color:rgb(var(--text-subdued-rgb));"></i>
</div>
<div class="sl-step-row overflow-hidden" data-step-for="${esc(item.ingredientId)}"
style="max-height:${isExpanded ? '52px' : '0'}; transition: max-height 0.2s ease;">
<div class="flex items-center gap-3 px-3 pb-3">
<button type="button" class="sl-step-minus w-8 h-8 rounded-full flex items-center justify-center text-lg font-bold shrink-0 active:scale-95" style="background:#2f2f2d; color:#ddd6ca; border:1px solid #444442;"></button>
<span class="sl-exp-amount text-[15px] font-bold tabular-nums" style="color:#f2efe8; min-width:40px; text-align:center;">${esc(formatQty(stepAmt))}</span>
<span class="text-[12px]" style="color:#9b978f;">${esc(unitLabel(item.unit))}</span>
<button type="button" class="sl-step-plus w-8 h-8 rounded-full flex items-center justify-center text-lg font-bold shrink-0 active:scale-95" style="background:#2f2f2d; color:#ddd6ca; border:1px solid #444442;">+</button>
<div class="flex-1"></div>
<button type="button" class="sl-step-confirm px-3 py-1.5 rounded-lg text-[12px] font-semibold shrink-0 active:scale-95" style="background:rgb(var(--accent-rgb)); color:#1a1a1a;">Dodaj</button>
style="max-height:${isExpanded ? '60px' : '0'}; transition: max-height 0.2s ease;">
<div class="flex items-center gap-2 px-3 pb-3">
<button type="button" class="sl-step-minus w-9 h-9 rounded-xl flex items-center justify-center shrink-0 active:scale-95" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" aria-label="Zmniejsz ilość">
<i class="fas fa-minus text-xs"></i>
</button>
<div class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:rgb(var(--card-soft-rgb));">
<span class="sl-exp-amount text-[14px] font-semibold tabular-nums" style="color:rgb(var(--text-body-rgb));">${esc(formatQty(stepAmt))}</span>
<span class="text-[12px] font-medium shrink-0" style="color:rgb(var(--text-dim-rgb));">${esc(unitLabel(item.unit))}</span>
</div>
<button type="button" class="sl-step-plus w-9 h-9 rounded-xl flex items-center justify-center shrink-0 active:scale-95" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" aria-label="Zwiększ ilość">
<i class="fas fa-plus text-xs"></i>
</button>
<button type="button" class="sl-step-confirm px-3 py-1.5 rounded-lg text-[12px] font-semibold shrink-0 active:scale-95" style="background:rgb(var(--text-body-rgb)); color:rgb(var(--app-bg-rgb));">Dodaj</button>
</div>
</div>
</div>
@@ -395,22 +399,22 @@ function boughtItemHtml(entry) {
const mediaFit = image?.endsWith('.svg') ? 'object-contain' : 'object-cover';
const mediaHtml = image
? `<img src="${esc(image)}" alt="" class="w-8 h-8 rounded-lg ${mediaFit} shrink-0 opacity-50">`
: `<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0 opacity-50" style="background:#2f2f2d;"><i class="fas ${icon} text-xs" style="color:#8f8b84;"></i></div>`;
: `<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0 opacity-50" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${icon} text-xs" style="color:rgb(var(--text-faint-rgb));"></i></div>`;
return `
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:#7c4a4a; box-shadow:0 2px 8px rgba(0,0,0,0.28);">
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:rgb(var(--danger-rgb)); box-shadow:0 2px 8px rgba(0,0,0,0.28);">
<div class="sl-swipe-bg-undo absolute inset-0 flex items-center pl-5 justify-start">
<i class="fas fa-rotate-left text-white text-lg"></i>
</div>
<div class="sl-swipe-inner flex items-center gap-3 py-1.5 px-3 rounded-xl" style="background:#2f2f2d; position:relative;" data-entry-id="${esc(entry.id)}">
<div class="sl-swipe-inner flex items-center gap-3 py-1.5 px-3 rounded-xl" style="background:rgb(var(--card-soft-rgb)); position:relative;" data-entry-id="${esc(entry.id)}">
<div class="shrink-0 w-6 h-6 rounded-full flex items-center justify-center" style="background:rgb(var(--success-rgb));">
<i class="fas fa-check text-[10px]" style="color:#1a1a1a;"></i>
<i class="fas fa-check text-[10px]" style="color:rgb(var(--on-accent-rgb));"></i>
</div>
${mediaHtml}
<div class="flex-1 min-w-0 opacity-60">
<span class="block text-[13px] font-medium leading-tight truncate line-through" style="color:#ddd6ca;">${esc(entry.name)}</span>
<span class="block text-[13px] font-medium leading-tight truncate line-through" style="color:rgb(var(--text-body-rgb));">${esc(entry.name)}</span>
</div>
<span class="text-[13px] tabular-nums shrink-0 opacity-60" style="color:#b7ada1;">${esc(formatQty(entry.addedAmount))} ${esc(unitLabel(entry.unit))}</span>
<span class="text-[13px] tabular-nums shrink-0 opacity-60" style="color:rgb(var(--text-muted-rgb));">${esc(formatQty(entry.addedAmount))} ${esc(unitLabel(entry.unit))}</span>
</div>
</div>`;
}
@@ -514,14 +518,12 @@ function undoBoughtEntry(entryId) {
/* ══════════════════════ RENDERING ══════════════════════ */
function renderProgress(activeItems, sessionLog) {
const activeIds = new Set(activeItems.map((i) => i.ingredientId));
const coveredIds = new Set(sessionLog.filter((e) => !activeIds.has(e.ingredientId)).map((e) => e.ingredientId));
const bought = coveredIds.size;
const total = activeItems.length + bought;
const touchedIds = new Set(sessionLog.map((e) => e.ingredientId));
const bought = touchedIds.size;
const untouched = activeItems.filter((i) => !touchedIds.has(i.ingredientId)).length;
const total = bought + untouched;
const textEl = document.getElementById('sl-progress-text');
const barEl = document.getElementById('sl-progress-bar');
if (textEl) textEl.textContent = `${bought}/${total}`;
if (barEl) barEl.style.width = total > 0 ? `${Math.round((bought / total) * 100)}%` : '0%';
}
@@ -533,27 +535,25 @@ function renderBoard() {
if (selectedDays.length === 0) {
root.innerHTML = `
<div class="flex flex-col items-center justify-center py-16 text-center">
<div class="w-14 h-14 rounded-2xl flex items-center justify-center mb-4" style="background:#393937;">
<i class="fas fa-calendar-days text-xl" style="color:#6d6c67;"></i>
<div class="w-14 h-14 rounded-2xl flex items-center justify-center mb-4" style="background:rgb(var(--card-rgb));">
<i class="fas fa-calendar-days text-xl" style="color:rgb(var(--text-subdued-rgb));"></i>
</div>
<p class="text-[14px] font-semibold mb-1" style="color:#ddd6ca;">Wybierz dni</p>
<p class="text-[12px] max-w-[14rem]" style="color:#9b978f;">Otwórz kalendarz i zaznacz dni, na które chcesz zrobić zakupy.</p>
<p class="text-[14px] font-semibold mb-1" style="color:rgb(var(--text-body-rgb));">Wybierz dni</p>
<p class="text-[12px] max-w-[14rem]" style="color:rgb(var(--text-dim-rgb));">Otwórz kalendarz i zaznacz dni, na które chcesz zrobić zakupy.</p>
</div>`;
return;
}
const activeItems = computeActiveItems();
const sessionLog = getSessionLog();
const sessionIds = new Set(sessionLog.map((e) => e.ingredientId));
if (activeItems.length === 0) {
root.innerHTML = `
<div class="flex flex-col items-center justify-center py-16 text-center">
<div class="w-14 h-14 rounded-2xl flex items-center justify-center mb-4" style="background:#393937;">
<div class="w-14 h-14 rounded-2xl flex items-center justify-center mb-4" style="background:rgb(var(--card-rgb));">
<i class="fas fa-check text-xl" style="color:rgb(var(--success-rgb));"></i>
</div>
<p class="text-[14px] font-semibold mb-1" style="color:#ddd6ca;">Wszystko masz</p>
<p class="text-[12px] max-w-[14rem]" style="color:#9b978f;">Spiżarnia pokrywa zapotrzebowanie na wybrane dni.</p>
<p class="text-[14px] font-semibold mb-1" style="color:rgb(var(--text-body-rgb));">Wszystko masz</p>
<p class="text-[12px] max-w-[14rem]" style="color:rgb(var(--text-dim-rgb));">Spiżarnia pokrywa zapotrzebowanie na wybrane dni.</p>
</div>`;
return;
}
@@ -564,11 +564,11 @@ function renderBoard() {
return `
<section class="mb-4">
<div class="flex items-center gap-1.5 mb-2 px-1">
<i class="fas ${icon} text-[10px]" style="color:#9b978f;"></i>
<p class="text-[10px] font-bold uppercase tracking-wider" style="color:#9b978f;">${esc(categoryLabel(cat))}</p>
<span class="text-[10px]" style="color:#6d6c67;">${catItems.length}</span>
<i class="fas ${icon} text-[10px]" style="color:rgb(var(--text-dim-rgb));"></i>
<p class="text-[10px] font-bold uppercase tracking-wider" style="color:rgb(var(--text-dim-rgb));">${esc(categoryLabel(cat))}</p>
<span class="text-[10px]" style="color:rgb(var(--text-subdued-rgb));">${catItems.length}</span>
</div>
${catItems.map((item) => activeItemHtml(item, sessionIds.has(item.ingredientId))).join('')}
${catItems.map((item) => activeItemHtml(item)).join('')}
</section>`;
}).join('');