Redesign meal planner

This commit is contained in:
2026-04-04 23:26:38 +02:00
parent b44b985e76
commit e83fb49d6b
3 changed files with 127 additions and 243 deletions

View File

@@ -182,24 +182,6 @@
box-shadow: var(--panel-shadow); box-shadow: var(--panel-shadow);
} }
#planner-summary-card {
position: relative;
overflow: hidden;
background: linear-gradient(145deg, rgba(var(--surface-rgb), 0.98) 0%, rgba(var(--surface-soft-rgb), 0.98) 100%) !important;
border-color: rgba(var(--warm-rgb), 0.26) !important;
box-shadow: var(--panel-shadow) !important;
}
#planner-summary-card::before {
content: '';
position: absolute;
inset: -20% auto auto -8%;
width: 9rem;
height: 9rem;
border-radius: 999px;
background: rgba(var(--warm-rgb), 0.14);
filter: blur(44px);
pointer-events: none;
}
.bg-amber-50 { background-color: rgba(var(--warm-rgb), 0.12) !important; } .bg-amber-50 { background-color: rgba(var(--warm-rgb), 0.12) !important; }
.bg-amber-50\/30 { background-color: rgba(var(--warm-rgb), 0.09) !important; } .bg-amber-50\/30 { background-color: rgba(var(--warm-rgb), 0.09) !important; }
.bg-amber-50\/50 { background-color: rgba(var(--warm-rgb), 0.14) !important; } .bg-amber-50\/50 { background-color: rgba(var(--warm-rgb), 0.14) !important; }
@@ -246,6 +228,15 @@
#pantry-view { #pantry-view {
background: rgb(var(--app-bg-rgb)) !important; background: rgb(var(--app-bg-rgb)) !important;
} }
#planner-view,
#planner-view > div:first-child,
#planner-scroll,
#calendar-swipe-zone,
#calendar-week-wrap,
#calendar-month-wrap,
#planner-meal-slots {
background: #2d2e2b !important;
}
#main-view > div:first-child, #main-view > div:first-child,
#planner-view > div:first-child, #planner-view > div:first-child,
#pantry-view > div:first-child, #pantry-view > div:first-child,
@@ -283,7 +274,6 @@
} }
#planner-picker-sheet, #planner-picker-sheet,
#planner-ing-sheet, #planner-ing-sheet,
#planner-copy-sheet,
#pv2-edit-sheet, #pv2-edit-sheet,
#mpe-sheet, #mpe-sheet,
#recipe-detail-view > div:last-child { #recipe-detail-view > div:last-child {
@@ -303,7 +293,6 @@
} }
#planner-picker-backdrop, #planner-picker-backdrop,
#planner-ing-backdrop, #planner-ing-backdrop,
#planner-copy-backdrop,
#pv2-edit-bg, #pv2-edit-bg,
#mpe-overlay { #mpe-overlay {
background: rgba(7, 6, 5, 0.42) !important; background: rgba(7, 6, 5, 0.42) !important;
@@ -313,13 +302,11 @@
/* Planner and common interactive surfaces */ /* Planner and common interactive surfaces */
#planner-open-ingredients, #planner-open-ingredients,
.planner-pick-recipe, .planner-pick-recipe,
.planner-copy-target,
#mpe-nutrition-section > div, #mpe-nutrition-section > div,
#mpe-add-area > div, #mpe-add-area > div,
#pv2-edit-nutrition ul { #pv2-edit-nutrition ul {
box-shadow: var(--panel-shadow) !important; box-shadow: var(--panel-shadow) !important;
} }
.planner-copy-target,
.planner-pick-recipe, .planner-pick-recipe,
.pv2-chip, .pv2-chip,
.pv2-cat-chip, .pv2-cat-chip,
@@ -329,8 +316,7 @@
#rd-tags span { #rd-tags span {
transition: transform 160ms ease, background-color 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease; transition: transform 160ms ease, background-color 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
} }
.planner-pick-recipe:hover, .planner-pick-recipe:hover { transform: translateY(-1px); }
.planner-copy-target:hover { transform: translateY(-1px); }
</style> </style>
</head> </head>
<body class="m-0 min-h-dvh bg-white text-gray-800 antialiased"> <body class="m-0 min-h-dvh bg-white text-gray-800 antialiased">
@@ -349,6 +335,6 @@
navigator.serviceWorker.register('./sw.js', { scope: './' }).catch(() => {}); navigator.serviceWorker.register('./sw.js', { scope: './' }).catch(() => {});
} }
</script> </script>
<script type="module" src="js/app.js?v=2"></script> <script type="module" src="js/app.js?v=18"></script>
</body> </body>
</html> </html>

View File

@@ -1,7 +1,7 @@
import { getRecipeListHTML, setupRecipeList } from './views/RecipeList.js?v=2'; import { getRecipeListHTML, setupRecipeList } from './views/RecipeList.js?v=2';
import { getFilterHTML, setupFilter } from './views/Filter.js?v=2'; import { getFilterHTML, setupFilter } from './views/Filter.js?v=2';
import { getRecipeDetailHTML, setupRecipeDetail } from './views/RecipeDetailV2.js?v=2'; import { getRecipeDetailHTML, setupRecipeDetail } from './views/RecipeDetailV2.js?v=2';
import { getMealPlannerHTML, setupMealPlanner } from './views/MealPlanner.js?v=4'; import { getMealPlannerHTML, setupMealPlanner } from './views/MealPlanner.js?v=20';
import { getPantryHTML, refreshPantry, setupPantry } from './views/Pantry.js?v=2'; import { getPantryHTML, refreshPantry, setupPantry } from './views/Pantry.js?v=2';
import { getMealPlanEditorHTML, setupMealPlanEditor } from './ui/mealPlanEditor.js?v=7'; import { getMealPlanEditorHTML, setupMealPlanEditor } from './ui/mealPlanEditor.js?v=7';

View File

@@ -1,7 +1,6 @@
import { INGREDIENTS, RECIPES } from '../data/catalog.js?v=2'; import { INGREDIENTS, RECIPES } from '../data/catalog.js?v=2';
import { MEAL_SLOTS } from '../planner/mealSlots.js'; import { MEAL_SLOTS } from '../planner/mealSlots.js';
import { import {
addDays,
addMonths, addMonths,
addWeeks, addWeeks,
sameMonth, sameMonth,
@@ -61,19 +60,20 @@ function syncTodayButton(mode, weekStart, monthAnchor, selected) {
export function getMealPlannerHTML() { export function getMealPlannerHTML() {
return ` return `
<div id="planner-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden bg-[#2d2e2b] z-10 pb-24"> <div id="planner-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden bg-[#2d2e2b] z-10 pb-24">
<div class="shrink-0 bg-white border-b border-gray-200 mt-3"> <div class="shrink-0 bg-[#2d2e2b] border-b border-[#444442] mt-3">
${createCalendarTopbarHTML({ ${createCalendarTopbarHTML({
titleId: 'cal-period-label', titleId: 'cal-period-label',
prevId: 'cal-prev', prevId: 'cal-prev',
todayId: 'cal-go-today', todayId: 'cal-go-today',
nextId: 'cal-next', nextId: 'cal-next',
titleClass: 'text-[18px] font-semibold text-[#ddd6ca] leading-none tracking-[-0.03em]',
})} })}
<div id="calendar-swipe-zone" class="overflow-x-hidden" style="touch-action: none"> <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" style="overflow: hidden; max-height: 10rem; opacity: 1; padding-bottom: 0.75rem"> <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">
${createCalendarWeekdayHeaderHTML()} ${createCalendarWeekdayHeaderHTML()}
<div id="calendar-week-grid" class="grid grid-cols-7 gap-1.5 max-w-full overflow-x-hidden"></div> <div id="calendar-week-grid" class="grid grid-cols-7 gap-1.5 max-w-full overflow-x-hidden"></div>
</div> </div>
<div id="calendar-month-wrap" class="px-3" style="overflow: hidden; max-height: 0; opacity: 0; padding-bottom: 0"> <div id="calendar-month-wrap" class="px-3 bg-[#2d2e2b]" style="overflow: hidden; max-height: 0; opacity: 0; padding-bottom: 0">
${createCalendarWeekdayHeaderHTML()} ${createCalendarWeekdayHeaderHTML()}
<div id="calendar-month-grid" class="grid grid-cols-7 gap-1.5"></div> <div id="calendar-month-grid" class="grid grid-cols-7 gap-1.5"></div>
</div> </div>
@@ -84,97 +84,73 @@ export function getMealPlannerHTML() {
</div> </div>
<div id="planner-scroll" class="flex-1 overflow-y-auto px-4 pt-3 pb-4 bg-[#2d2e2b]"> <div id="planner-scroll" class="flex-1 overflow-y-auto px-4 pt-3 pb-4 bg-[#2d2e2b]">
<div class="flex items-center justify-between mb-2"> <div id="planner-summary-card" class="mb-3">
<p id="planner-day-heading" class="text-[13px] font-semibold text-gray-900 tabular-nums"></p> <div class="h-full flex flex-col" style="background:#2d2e2b !important; background-image:none !important; box-shadow:none !important;">
<button type="button" id="planner-copy-day" class="shrink-0 text-[11px] font-semibold text-gray-500 hover:text-gray-900 px-2.5 py-1 rounded-lg hover:bg-gray-100 transition-colors flex items-center gap-1.5"> <p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Wartości odżywcze</p>
<i class="fas fa-copy text-[9px]"></i>Kopiuj dzień <div class="flex-1 flex items-center">
</button> <div class="w-full rounded-xl border px-3 py-2.5" style="border-color:#444442 !important;">
<div class="grid grid-cols-4 gap-3 text-left">
<div class="min-w-0">
<span id="planner-nutrition-kcal" class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">—</span>
<span class="text-[9px] text-gray-500">kcal</span>
</div> </div>
<div id="planner-summary-card" class="rounded-xl border border-amber-200/80 bg-[#2d2e2b] p-2.5 shadow-sm mb-3"> <div class="min-w-0">
<div class="flex items-start justify-between gap-2 mb-2"> <span id="planner-nutrition-p" class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">—</span>
<div> <span class="text-[9px] text-gray-500">Białko</span>
<p class="text-[10px] font-semibold uppercase tracking-wide text-amber-900/70">Dziś — podsumowanie</p>
<p id="planner-summary-kcal" class="text-xl font-bold text-gray-900 tabular-nums leading-tight mt-0.5">0 <span class="text-[13px] font-semibold text-gray-500">kcal</span></p>
</div> </div>
<button type="button" id="planner-toggle-nutrition" class="shrink-0 flex items-center gap-1 text-[11px] font-semibold text-amber-900/80 hover:text-gray-900 py-1 px-2 rounded-lg hover:bg-amber-100/50 transition-colors" aria-expanded="false"> <div class="min-w-0">
Szczegóły <span id="planner-nutrition-c" class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">—</span>
<i class="fas fa-chevron-down text-[9px] transition-transform" id="planner-nutrition-chevron" aria-hidden="true"></i> <span class="text-[9px] text-gray-500">Węgle</span>
</button>
</div> </div>
<div id="planner-macro-row" class="flex gap-2 mb-0"> <div class="min-w-0">
<div class="flex-1 min-w-0 rounded-lg bg-white/80 border border-amber-100 px-2 py-1.5 text-center"> <span id="planner-nutrition-f" class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">—</span>
<p class="text-[9px] font-semibold text-gray-500 uppercase">B</p> <span class="text-[9px] text-gray-500">Tłuszcze</span>
<p id="planner-macro-p" class="text-xs font-bold text-gray-900 tabular-nums">0 g</p>
</div>
<div class="flex-1 min-w-0 rounded-lg bg-white/80 border border-amber-100 px-2 py-1.5 text-center">
<p class="text-[9px] font-semibold text-gray-500 uppercase">T</p>
<p id="planner-macro-f" class="text-xs font-bold text-gray-900 tabular-nums">0 g</p>
</div>
<div class="flex-1 min-w-0 rounded-lg bg-white/80 border border-amber-100 px-2 py-1.5 text-center">
<p class="text-[9px] font-semibold text-gray-500 uppercase">W</p>
<p id="planner-macro-c" class="text-xs font-bold text-gray-900 tabular-nums">0 g</p>
</div> </div>
</div> </div>
<div id="planner-nutrition-details" class="hidden mt-3 pt-3 border-t border-amber-200/60">
<ul class="space-y-0 divide-y divide-amber-100/80 text-sm">
<li class="flex justify-between py-2 font-bold"><span class="text-gray-800">Kalorie</span><span id="planner-detail-kcal" class="text-gray-900 tabular-nums">0 kcal</span></li>
<li class="flex justify-between py-2"><span class="text-gray-700 font-medium">Białko</span><span id="planner-detail-p" class="font-medium text-gray-900 tabular-nums">0 g</span></li>
<li class="flex justify-between py-2"><span class="text-gray-700 font-medium">Tłuszcze</span><span id="planner-detail-f" class="font-medium text-gray-900 tabular-nums">0 g</span></li>
<li class="flex justify-between py-2"><span class="text-gray-700 font-medium">Węglowodany</span><span id="planner-detail-c" class="font-medium text-gray-900 tabular-nums">0 g</span></li>
</ul>
<p id="planner-summary-hint" class="text-[11px] text-gray-500 mt-2">Suma z zaplanowanych posiłków (porcje × wartości z przepisu).</p>
</div> </div>
</div> </div>
<button type="button" id="planner-open-ingredients" class="w-full mb-3 flex items-center justify-center gap-2 py-2.5 rounded-xl border border-dashed border-gray-300 bg-white text-[13px] font-semibold text-gray-800 hover:border-gray-400 hover:bg-gray-50 transition-colors"> </div>
<i class="fas fa-shopping-basket text-gray-500 text-xs" aria-hidden="true"></i> </div>
<button type="button" id="planner-open-ingredients" class="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>
Składniki na ten dzień Składniki na ten dzień
</button> </button>
<div id="planner-meal-slots" class="space-y-3 pb-2"></div> <div id="planner-meal-slots" class="space-y-3 pb-2 bg-[#2d2e2b]"></div>
</div> </div>
<div id="planner-picker-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div> <div id="planner-picker-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
<div id="planner-picker-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-picker-title" aria-modal="true"> <div id="planner-picker-sheet" class="absolute left-0 right-0 z-[50] rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; 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-picker-title" aria-modal="true">
<div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć"> <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-gray-200 rounded-full mx-auto mb-2.5" aria-hidden="true"></div> <div class="w-10 h-1 bg-[#6d6c67]/75 rounded-full mx-auto mb-2.5" aria-hidden="true"></div>
<h2 id="planner-picker-title" class="text-[15px] font-bold text-gray-900 leading-tight pr-2">Wybierz przepis</h2> <h2 id="planner-picker-title" class="text-[15px] font-bold text-[#ddd6ca] leading-tight pr-2">Wybierz przepis</h2>
<p id="planner-picker-sub" class="text-[11px] text-gray-500 mt-1"></p> <p id="planner-picker-sub" class="text-[11px] text-[#9b978f] mt-1"></p>
</div> </div>
<div class="shrink-0 px-4 pt-2 pb-2"> <div class="shrink-0 px-4 pt-2 pb-2">
<input type="text" id="planner-picker-search" class="w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-sm outline-none focus:border-gray-400 placeholder:text-gray-400" placeholder="Szukaj przepisu…" /> <input type="text" id="planner-picker-search" class="w-full rounded-xl border border-[#444442] bg-[#2f2f2d] px-3 py-2 text-sm text-[#ddd6ca] outline-none focus:border-[#6d6c67] placeholder:text-[#7d7a74]" placeholder="Szukaj przepisu…" />
</div> </div>
<div id="planner-picker-list" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2.5 pb-8 space-y-2"></div> <div id="planner-picker-list" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2.5 pb-8 space-y-2"></div>
</div> </div>
<div id="planner-ing-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div> <div id="planner-ing-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
<div id="planner-ing-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-ing-title" aria-modal="true"> <div id="planner-ing-sheet" class="absolute left-0 right-0 z-[50] rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; 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-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć"> <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-gray-200 rounded-full mx-auto mb-2.5" aria-hidden="true"></div> <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-gray-900 leading-tight pr-2">Składniki i spiżarnia</h2> <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-gray-500 mt-1">Porównanie potrzeb z zapasami.</p> <p id="planner-ing-sub" class="text-[11px] text-[#9b978f] mt-1">Porównanie potrzeb z zapasami.</p>
</div> </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-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-gray-100 bg-white space-y-2"> <div id="planner-ing-footer" class="shrink-0 p-4 pt-2 pb-5 border-t border-[#444442] bg-[#2d2e2b] 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"> <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> <i class="fas fa-cart-plus text-xs" aria-hidden="true"></i>
Dodaj braki na dziś do listy Dodaj braki na dziś do listy
</button> </button>
<button type="button" id="planner-ing-add-btn" class="hidden w-full border border-gray-200 bg-white text-gray-800 hover:bg-gray-50 py-2.5 rounded-xl font-semibold text-[13px] flex items-center justify-center gap-2 transition-colors"> <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-gray-500 text-[11px]" aria-hidden="true"></i> <i class="fas fa-calendar-week text-[#9b978f] text-[11px]" aria-hidden="true"></i>
Dodaj braki na cały tydzień Dodaj braki na cały tydzień
</button> </button>
</div> </div>
</div> </div>
<div id="planner-copy-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
<div id="planner-copy-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-modal="true">
<div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone>
<div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2.5"></div>
<h2 class="text-[15px] font-bold text-gray-900 leading-tight">Kopiuj plan dnia</h2>
<p id="planner-copy-sub" class="text-[11px] text-gray-500 mt-1">Wybierz dzień docelowy.</p>
</div>
<div id="planner-copy-list" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2.5 pb-8 space-y-2"></div>
</div>
<div id="planner-toast" class="pointer-events-none absolute left-4 right-4 bottom-28 z-[55] opacity-0 translate-y-2 transition-all duration-300" role="status"> <div id="planner-toast" class="pointer-events-none absolute left-4 right-4 bottom-28 z-[55] opacity-0 translate-y-2 transition-all duration-300" role="status">
<div class="rounded-xl bg-gray-900 text-white text-sm font-medium px-4 py-3 shadow-lg text-center" id="planner-toast-text"></div> <div class="rounded-xl bg-gray-900 text-white text-sm font-medium px-4 py-3 shadow-lg text-center" id="planner-toast-text"></div>
</div> </div>
@@ -346,30 +322,26 @@ function bindPlannerSheetDragClose(sheet, closeFn) {
function renderDayContent(state) { function renderDayContent(state) {
const sel = state.selected; const sel = state.selected;
const heading = document.getElementById('planner-day-heading');
if (heading) {
const wd = WEEKDAYS_LONG[sel.getDay()];
heading.textContent = `${wd}, ${sel.getDate()} ${CALENDAR_MONTHS_SHORT[sel.getMonth()]}`;
}
const dayPlan = getDayPlan(state.plans, sel); const dayPlan = getDayPlan(state.plans, sel);
const totals = sumDayNutrition(dayPlan); const totals = sumDayNutrition(dayPlan);
const kcalEl = document.getElementById('planner-summary-kcal'); const setText = (id, value) => {
if (kcalEl) { const el = document.getElementById(id);
kcalEl.innerHTML = totals.mealCount === 0 if (el) el.textContent = value;
? `— <span class="text-sm font-semibold text-gray-500">kcal</span>` };
: `${totals.kcal} <span class="text-sm font-semibold text-gray-500">kcal</span>`; const setGrams = (id, value) => {
} const el = document.getElementById(id);
const fmt = (n) => `${n} g`; if (!el) return;
document.getElementById('planner-macro-p').textContent = totals.mealCount ? fmt(totals.protein) : '—'; el.innerHTML = value === null
document.getElementById('planner-macro-f').textContent = totals.mealCount ? fmt(totals.fat) : '—'; ? '—'
document.getElementById('planner-macro-c').textContent = totals.mealCount ? fmt(totals.carbs) : '—'; : `${value}<span class="ml-0.5 text-[12px] font-medium text-[#9b978f]">g</span>`;
};
document.getElementById('planner-detail-kcal').textContent = `${totals.kcal} kcal`; const hasMeals = totals.mealCount > 0;
document.getElementById('planner-detail-p').textContent = fmt(totals.protein); setText('planner-nutrition-kcal', hasMeals ? String(totals.kcal) : '—');
document.getElementById('planner-detail-f').textContent = fmt(totals.fat); setGrams('planner-nutrition-p', hasMeals ? totals.protein : null);
document.getElementById('planner-detail-c').textContent = fmt(totals.carbs); setGrams('planner-nutrition-f', hasMeals ? totals.fat : null);
setGrams('planner-nutrition-c', hasMeals ? totals.carbs : null);
const ingBtn = document.getElementById('planner-open-ingredients'); const ingBtn = document.getElementById('planner-open-ingredients');
if (ingBtn) { if (ingBtn) {
@@ -407,7 +379,7 @@ function renderDayContent(state) {
if (entry?.recipeId && RECIPES[entry.recipeId]) slotKcal += computeEntryNutrition(entry).kcal; if (entry?.recipeId && RECIPES[entry.recipeId]) slotKcal += computeEntryNutrition(entry).kcal;
}); });
const kcalBadge = slotKcal > 0 const kcalBadge = slotKcal > 0
? `<span class="text-[10px] font-semibold text-amber-600 tabular-nums shrink-0 ml-auto">${slotKcal} kcal</span>` ? `<span class="text-[10px] font-semibold text-[#f2efe8] tabular-nums shrink-0 ml-auto">${slotKcal} kcal</span>`
: ''; : '';
const countLabel = entries.length > 1 const countLabel = entries.length > 1
? `<span class="text-[10px] font-semibold text-gray-400 tabular-nums shrink-0">${entries.length} dania</span>` ? `<span class="text-[10px] font-semibold text-gray-400 tabular-nums shrink-0">${entries.length} dania</span>`
@@ -424,30 +396,30 @@ function renderDayContent(state) {
(entry.addedIngredients?.length > 0) || (entry.addedIngredients?.length > 0) ||
(entry.substitutions && Object.keys(entry.substitutions).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 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-gray-300">·</span>×${servings}` : ''; const servLabel = servings > 1 ? `<span class="mx-1.5 text-[#6d6c67]">·</span>×${servings}` : '';
return ` return `
<div class="rounded-lg border border-gray-200 bg-white p-2 shadow-sm" data-slot-id="${slot.id}" data-entry-id="${eid}"> <div class="rounded-lg bg-[#2d2e2b] p-2" data-slot-id="${slot.id}" data-entry-id="${eid}">
<div class="flex items-start justify-between gap-2"> <div class="flex items-start justify-between gap-2">
<div class="flex items-center gap-2 min-w-0 cursor-pointer planner-open-recipe" data-recipe-id="${escapeHtml(recipe.id)}"> <div class="flex items-center gap-2 min-w-0 cursor-pointer planner-open-recipe" data-recipe-id="${escapeHtml(recipe.id)}">
<div class="w-8 h-8 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0"> <div class="w-8 h-8 rounded-lg bg-[#3a3a37] overflow-hidden shrink-0">
${recipe.image ${recipe.image
? `<img src="${escapeHtml(recipe.image)}" alt="" class="w-full h-full object-cover">` ? `<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>`} : `<span class="w-full h-full flex items-center justify-center text-white text-[8px] font-medium">${escapeHtml(recipe.thumbLabel)}</span>`}
</div> </div>
<div class="min-w-0"> <div class="min-w-0">
<div class="flex items-center"><p class="text-[13px] font-bold text-gray-900 truncate underline decoration-1 underline-offset-2">${escapeHtml(recipe.title)}</p>${customDot}</div> <div class="flex items-center"><p class="text-[13px] font-bold text-[#ddd6ca] truncate underline decoration-1 underline-offset-2">${escapeHtml(recipe.title)}</p>${customDot}</div>
<p class="text-[11px] text-gray-500 mt-0.5 tabular-nums"> <p class="text-[11px] text-[#9b978f] mt-0.5 tabular-nums">
<i class="fas fa-clock text-gray-400 mr-0.5" aria-hidden="true"></i>${recipe.minutes} min <i class="fas fa-clock text-[#7d7a74] mr-0.5" aria-hidden="true"></i>${recipe.minutes} min
<span class="mx-1.5 text-gray-300">·</span> <span class="mx-1.5 text-[#6d6c67]">·</span>
<i class="fas fa-fire text-gray-400 mr-0.5" aria-hidden="true"></i>${entryN.kcal} kcal${servLabel} <i class="fas fa-fire text-[#7d7a74] mr-0.5" aria-hidden="true"></i>${entryN.kcal} kcal${servLabel}
</p> </p>
</div> </div>
</div> </div>
<div class="flex items-center gap-1 shrink-0"> <div class="flex items-center gap-1 shrink-0">
<button type="button" class="planner-edit-meal w-6 h-6 rounded-full border border-gray-200 text-gray-400 hover:text-gray-900 hover:border-gray-400 hover:bg-gray-50 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-[#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">
<i class="fas fa-pencil text-[9px]" aria-hidden="true"></i> <i class="fas fa-pencil text-[9px]" aria-hidden="true"></i>
</button> </button>
<button type="button" class="planner-clear-meal w-6 h-6 rounded-full border border-gray-200 text-gray-400 hover:text-red-600 hover:border-red-200 hover:bg-red-50 transition-colors flex items-center justify-center" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Usuń ten przepis"> <button type="button" class="planner-clear-meal w-6 h-6 rounded-full border border-[#444442] text-[#9b978f] hover:text-red-400 hover:border-red-300/60 hover:bg-[#3a2326] transition-colors flex items-center justify-center" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Usuń ten przepis">
<i class="fas fa-times text-[9px]" aria-hidden="true"></i> <i class="fas fa-times text-[9px]" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -457,27 +429,25 @@ function renderDayContent(state) {
if (isSkipped) { if (isSkipped) {
return ` return `
<div class="rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden opacity-60" data-slot-id="${slot.id}"> <div class="rounded-xl bg-[#393937] overflow-hidden opacity-60" style="background:#393937 !important;" data-slot-id="${slot.id}">
<div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100 bg-gray-50/90"> <div class="flex items-center gap-2 px-3 py-2 border-b border-[#444442] bg-[#393937]" style="background:#393937 !important;">
<span class="w-7 h-7 rounded-lg bg-gray-100 flex items-center justify-center text-gray-400 shrink-0"> <i class="fas ${slot.icon} w-7 text-center text-[13px] text-[#7d7a74] shrink-0" aria-hidden="true"></i>
<i class="fas ${slot.icon} text-[13px]" aria-hidden="true"></i> <span class="text-[13px] font-semibold text-[#9b978f] truncate min-w-0 flex-1">${slot.label}</span>
</span>
<span class="text-[13px] font-semibold text-gray-400 truncate min-w-0 flex-1">${slot.label}</span>
</div> </div>
<div class="p-2.5 flex items-center justify-between"> <div class="p-2.5 flex items-center justify-between">
<span class="text-xs text-gray-400 italic"><i class="fas fa-forward text-[9px] mr-1.5"></i>Pominięto</span> <span class="text-xs text-[#9b978f] italic"><i class="fas fa-forward text-[9px] mr-1.5"></i>Pominięto</span>
<button type="button" class="planner-unskip text-[11px] font-semibold text-gray-500 hover:text-gray-900 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-slot-id="${slot.id}">Cofnij</button> <button type="button" class="planner-unskip text-[11px] font-semibold text-[#9b978f] hover:text-[#ddd6ca] px-2 py-1 rounded-lg hover:bg-[#3a3a37] transition-colors" data-slot-id="${slot.id}">Cofnij</button>
</div> </div>
</div>`; </div>`;
} }
const addLabel = entries.length === 0 ? 'Dodaj przepis' : 'Dodaj kolejny'; const addLabel = entries.length === 0 ? 'Dodaj przepis' : 'Dodaj kolejny';
const addClasses = entries.length === 0 const addClasses = entries.length === 0
? 'planner-add-meal flex-1 py-2 rounded-lg border border-dashed border-gray-200 text-[13px] font-semibold text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-colors' ? 'planner-add-meal flex-1 py-2 rounded-lg border border-dashed border-[#444442] text-[13px] font-semibold text-[#d7d2c8] hover:bg-[#3a3a37] hover:border-[#6d6c67] transition-colors'
: 'planner-add-meal w-full py-1.5 rounded-lg border border-dashed border-gray-200 text-xs font-semibold text-gray-600 hover:bg-gray-50 hover:border-gray-300 transition-colors'; : 'planner-add-meal w-full py-1.5 rounded-lg border border-dashed border-[#444442] text-xs font-semibold text-[#d7d2c8] hover:bg-[#3a3a37] hover:border-[#6d6c67] transition-colors';
const skipBtn = entries.length === 0 const skipBtn = entries.length === 0
? `<button type="button" class="planner-skip-meal shrink-0 py-2 px-3 rounded-lg text-[11px] font-semibold text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition-colors" data-slot-id="${slot.id}"><i class="fas fa-forward text-[9px] mr-1"></i>Pomijam</button>` ? `<button type="button" class="planner-skip-meal shrink-0 py-2 px-3 rounded-lg text-[11px] font-semibold text-[#9b978f] hover:text-[#ddd6ca] hover:bg-[#3a3a37] transition-colors" data-slot-id="${slot.id}"><i class="fas fa-forward text-[9px] mr-1"></i>Pomijam</button>`
: ''; : '';
const bottomRow = entries.length === 0 const bottomRow = entries.length === 0
@@ -485,12 +455,10 @@ function renderDayContent(state) {
: `<button type="button" class="${addClasses}" data-slot-id="${slot.id}"><i class="fas fa-plus text-[10px] mr-1 opacity-70" aria-hidden="true"></i>${addLabel}</button>`; : `<button type="button" class="${addClasses}" data-slot-id="${slot.id}"><i class="fas fa-plus text-[10px] mr-1 opacity-70" aria-hidden="true"></i>${addLabel}</button>`;
return ` return `
<div class="rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden" data-slot-id="${slot.id}"> <div class="rounded-xl bg-[#393937] overflow-hidden" style="background:#393937 !important;" data-slot-id="${slot.id}">
<div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100 bg-gray-50/90"> <div class="flex items-center gap-2 px-3 py-2 border-b border-[#444442] bg-[#393937]" style="background:#393937 !important;">
<span class="w-7 h-7 rounded-lg bg-gray-100 flex items-center justify-center text-gray-500 shrink-0"> <i class="fas ${slot.icon} w-7 text-center text-[13px] text-[#9b978f] shrink-0" aria-hidden="true"></i>
<i class="fas ${slot.icon} text-[13px]" aria-hidden="true"></i> <span class="text-[13px] font-semibold text-[#ddd6ca] truncate min-w-0 flex-1">${slot.label}</span>
</span>
<span class="text-[13px] font-semibold text-gray-900 truncate min-w-0 flex-1">${slot.label}</span>
${countLabel} ${countLabel}
${kcalBadge} ${kcalBadge}
</div> </div>
@@ -533,18 +501,18 @@ function getRecentRecipeIds(plans, limit = 5) {
function recipeCardHtml(r) { function recipeCardHtml(r) {
return ` return `
<button type="button" class="planner-pick-recipe w-full flex gap-2.5 p-2.5 rounded-xl border border-gray-200 bg-gray-50/80 hover:border-gray-900 hover:bg-white text-left transition-all" data-recipe-id="${r.id}"> <button type="button" class="planner-pick-recipe w-full flex gap-2.5 p-2.5 rounded-xl border border-[#444442] bg-[#2d2e2b] hover:border-[#6d6c67] hover:bg-[#3a3a37] text-left transition-all" data-recipe-id="${r.id}">
<div class="w-11 h-11 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0"> <div class="w-11 h-11 rounded-lg bg-[#3a3a37] overflow-hidden shrink-0">
${r.image ${r.image
? `<img src="${escapeHtml(r.image)}" alt="" class="w-full h-full object-cover">` ? `<img src="${escapeHtml(r.image)}" alt="" class="w-full h-full object-cover">`
: `<span class="w-full h-full flex items-center justify-center text-white text-[9px] font-medium">${escapeHtml(r.thumbLabel)}</span>`} : `<span class="w-full h-full flex items-center justify-center text-white text-[9px] font-medium">${escapeHtml(r.thumbLabel)}</span>`}
</div> </div>
<div class="min-w-0 flex-1 py-0.5"> <div class="min-w-0 flex-1 py-0.5">
<p class="text-[13px] font-bold text-gray-900 line-clamp-2">${escapeHtml(r.title)}</p> <p class="text-[13px] font-bold text-[#ddd6ca] line-clamp-2">${escapeHtml(r.title)}</p>
<p class="text-[11px] text-gray-500 mt-1 tabular-nums"> <p class="text-[11px] text-[#9b978f] mt-1 tabular-nums">
<i class="fas fa-fire text-gray-400 mr-0.5" aria-hidden="true"></i>${r.nutritionPerServing.kcal} kcal <i class="fas fa-fire text-[#7d7a74] mr-0.5" aria-hidden="true"></i>${r.nutritionPerServing.kcal} kcal
<span class="mx-1 text-gray-300">·</span> <span class="mx-1 text-[#6d6c67]">·</span>
<i class="fas fa-clock text-gray-400 mr-0.5" aria-hidden="true"></i>${r.minutes} min <i class="fas fa-clock text-[#7d7a74] mr-0.5" aria-hidden="true"></i>${r.minutes} min
</p> </p>
</div> </div>
</button>`; </button>`;
@@ -573,11 +541,11 @@ function renderPickerList(slotId, plans, query = '') {
: allRecipes; : allRecipes;
if (filtered.length === 0 && q) { if (filtered.length === 0 && q) {
list.innerHTML = '<p class="text-sm text-gray-500 text-center py-6">Brak wyników.</p>'; list.innerHTML = '<p class="text-sm text-[#9b978f] text-center py-6">Brak wyników.</p>';
return; return;
} }
if (filtered.length === 0) { if (filtered.length === 0) {
list.innerHTML = '<p class="text-sm text-gray-500 text-center py-6">Brak dopasowanych przepisów.</p>'; list.innerHTML = '<p class="text-sm text-[#9b978f] text-center py-6">Brak dopasowanych przepisów.</p>';
return; return;
} }
@@ -589,7 +557,7 @@ function renderPickerList(slotId, plans, query = '') {
if (recentInSlot.length > 0) { if (recentInSlot.length > 0) {
html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1"><i class="fas fa-history text-[9px] mr-1"></i>Ostatnio używane</p>`; html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1"><i class="fas fa-history text-[9px] mr-1"></i>Ostatnio używane</p>`;
html += recentInSlot.map(recipeCardHtml).join(''); html += recentInSlot.map(recipeCardHtml).join('');
html += `<div class="border-t border-gray-100 my-2"></div>`; html += `<div class="border-t border-[#444442] my-2"></div>`;
html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1">Wszystkie</p>`; html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1">Wszystkie</p>`;
} }
} }
@@ -659,7 +627,7 @@ function renderIngredientsSheet(state) {
if (subEl) subEl.textContent = 'Porównanie potrzeb z zapasami w spiżarni.'; if (subEl) subEl.textContent = 'Porównanie potrzeb z zapasami w spiżarni.';
if (!today || today.items.length === 0) { if (!today || today.items.length === 0) {
body.innerHTML = '<p class="text-sm text-gray-500 text-center py-8">Najpierw zaplanuj posiłki.</p>'; body.innerHTML = '<p class="text-sm text-[#9b978f] text-center py-8">Najpierw zaplanuj posiłki.</p>';
updateIngButtons(state); updateIngButtons(state);
return; return;
} }
@@ -669,22 +637,22 @@ function renderIngredientsSheet(state) {
let html = ''; let html = '';
if (shortItems.length === 0) { if (shortItems.length === 0) {
html += `<div class="rounded-xl bg-emerald-50 border border-emerald-200/80 p-3 mb-4 flex items-center gap-2.5"> 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-emerald-100 flex items-center justify-center shrink-0"> <div class="w-8 h-8 rounded-full bg-[#24352a] flex items-center justify-center shrink-0">
<i class="fas fa-check text-emerald-600 text-sm"></i> <i class="fas fa-check text-emerald-600 text-sm"></i>
</div> </div>
<div> <div>
<p class="text-[13px] font-semibold text-emerald-800">Wszystko masz w spiżarni</p> <p class="text-[13px] font-semibold text-emerald-300">Wszystko masz w spiżarni</p>
<p class="text-[11px] text-emerald-600/80">${today.items.length} ${plIngredientWord(today.items.length)} — zapasy wystarczą</p> <p class="text-[11px] text-emerald-600/80">${today.items.length} ${plIngredientWord(today.items.length)} — zapasy wystarczą</p>
</div> </div>
</div>`; </div>`;
} else { } else {
html += `<div class="rounded-xl bg-red-50 border border-red-200/80 p-3 mb-4 flex items-center gap-2.5"> 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-red-100 flex items-center justify-center shrink-0"> <div class="w-8 h-8 rounded-full bg-[#3a2326] flex items-center justify-center shrink-0">
<i class="fas fa-exclamation text-red-500 text-sm"></i> <i class="fas fa-exclamation text-red-500 text-sm"></i>
</div> </div>
<div> <div>
<p class="text-[13px] font-semibold text-red-800">${shortItems.length} ${plIngredientWord(shortItems.length)} do kupienia</p> <p class="text-[13px] font-semibold text-red-300">${shortItems.length} ${plIngredientWord(shortItems.length)} do kupienia</p>
<p class="text-[11px] text-red-600/80">Brakuje składników na zaplanowane posiłki</p> <p class="text-[11px] text-red-600/80">Brakuje składników na zaplanowane posiłki</p>
</div> </div>
</div>`; </div>`;
@@ -695,15 +663,15 @@ function renderIngredientsSheet(state) {
<p class="text-[10px] font-bold text-red-400 uppercase tracking-wider mb-2 px-0.5"> <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 <i class="fas fa-cart-shopping text-[9px] mr-1"></i>Do kupienia
</p> </p>
<ul class="border border-red-100/80 rounded-xl overflow-hidden bg-white divide-y divide-red-50"> <ul class="border border-red-300/30 rounded-xl overflow-hidden bg-[#2d2e2b] divide-y divide-[#3a2326]">
${shortItems.map((ing) => ` ${shortItems.map((ing) => `
<li class="flex items-start gap-3 py-3 px-3"> <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="w-2 h-2 rounded-full bg-red-400 mt-1.5 shrink-0"></div>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<p class="text-[13px] font-semibold text-gray-900">${escapeHtml(ing.name)}</p> <p class="text-[13px] font-semibold text-[#ddd6ca]">${escapeHtml(ing.name)}</p>
<p class="text-[11px] text-gray-500 mt-0.5"> <p class="text-[11px] text-[#9b978f] mt-0.5">
potrzeba <span class="font-medium text-gray-700">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span> potrzeba <span class="font-medium text-[#d7d2c8]">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
<span class="mx-1 text-gray-300">&middot;</span> <span class="mx-1 text-[#6d6c67]">&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> 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> </p>
</div> </div>
@@ -721,15 +689,15 @@ function renderIngredientsSheet(state) {
<p class="text-[10px] font-bold text-emerald-500 uppercase tracking-wider mb-2 px-0.5"> <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 <i class="fas fa-check text-[9px] mr-1"></i>W spiżarni
</p> </p>
<ul class="border border-gray-100 rounded-xl overflow-hidden bg-white divide-y divide-gray-50"> <ul class="border border-[#444442] rounded-xl overflow-hidden bg-[#2d2e2b] divide-y divide-[#353632]">
${okItems.map((ing) => ` ${okItems.map((ing) => `
<li class="flex items-start gap-3 py-2.5 px-3"> <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="w-2 h-2 rounded-full bg-emerald-400 mt-1.5 shrink-0"></div>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<p class="text-[13px] font-medium text-gray-700">${escapeHtml(ing.name)}</p> <p class="text-[13px] font-medium text-[#d7d2c8]">${escapeHtml(ing.name)}</p>
<p class="text-[11px] text-gray-400 mt-0.5"> <p class="text-[11px] text-[#9b978f] mt-0.5">
potrzeba <span class="font-medium">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span> potrzeba <span class="font-medium text-[#d7d2c8]">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
<span class="mx-1 text-gray-300">&middot;</span> <span class="mx-1 text-[#6d6c67]">&middot;</span>
masz <span class="font-medium text-emerald-600">${formatAmount(ing.pantryQty)} ${escapeHtml(ing.pantryUnit)}</span> masz <span class="font-medium text-emerald-600">${formatAmount(ing.pantryQty)} ${escapeHtml(ing.pantryUnit)}</span>
</p> </p>
</div> </div>
@@ -748,7 +716,7 @@ function renderIngredientsSheet(state) {
const wd = WEEKDAYS_LONG[day.date.getDay()]; const wd = WEEKDAYS_LONG[day.date.getDay()];
const label = `${wd}, ${day.date.getDate()} ${CALENDAR_MONTHS_SHORT[day.date.getMonth()]}`; const label = `${wd}, ${day.date.getDate()} ${CALENDAR_MONTHS_SHORT[day.date.getMonth()]}`;
const shorts = day.items.filter((it) => !it.enough); const shorts = day.items.filter((it) => !it.enough);
return `<div class="rounded-xl border border-amber-200/80 bg-amber-50/50 p-3"> return `<div class="rounded-xl border border-amber-200/80 bg-[#2d2e2b] p-3">
<p class="text-[12px] font-semibold text-amber-900"> <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)} <i class="fas fa-calendar-day text-[10px] mr-1.5 text-amber-500"></i>${escapeHtml(label)}
</p> </p>
@@ -797,7 +765,6 @@ export function setupMealPlanner() {
monthAnchor: startOfDay(new Date()), monthAnchor: startOfDay(new Date()),
selected: startOfDay(new Date()), selected: startOfDay(new Date()),
plans, plans,
nutritionExpanded: false,
pickerSlot: null, pickerSlot: null,
}; };
@@ -808,8 +775,6 @@ export function setupMealPlanner() {
const pickerSheet = document.getElementById('planner-picker-sheet'); const pickerSheet = document.getElementById('planner-picker-sheet');
const ingBackdrop = document.getElementById('planner-ing-backdrop'); const ingBackdrop = document.getElementById('planner-ing-backdrop');
const ingSheet = document.getElementById('planner-ing-sheet'); const ingSheet = document.getElementById('planner-ing-sheet');
const copyBackdrop = document.getElementById('planner-copy-backdrop');
const copySheet = document.getElementById('planner-copy-sheet');
const rerender = () => { const rerender = () => {
syncModeToggle(state.mode); syncModeToggle(state.mode);
@@ -883,16 +848,6 @@ export function setupMealPlanner() {
rerender(); rerender();
}); });
document.getElementById('planner-toggle-nutrition')?.addEventListener('click', () => {
state.nutritionExpanded = !state.nutritionExpanded;
const details = document.getElementById('planner-nutrition-details');
const chev = document.getElementById('planner-nutrition-chevron');
const btn = document.getElementById('planner-toggle-nutrition');
if (details) details.classList.toggle('hidden', !state.nutritionExpanded);
if (chev) chev.classList.toggle('rotate-180', state.nutritionExpanded);
if (btn) btn.setAttribute('aria-expanded', state.nutritionExpanded ? 'true' : 'false');
});
document.getElementById('planner-meal-slots')?.addEventListener('click', (e) => { document.getElementById('planner-meal-slots')?.addEventListener('click', (e) => {
const skipBtn = e.target.closest('.planner-skip-meal'); const skipBtn = e.target.closest('.planner-skip-meal');
if (skipBtn) { if (skipBtn) {
@@ -1014,63 +969,6 @@ export function setupMealPlanner() {
closeSheet(ingBackdrop, ingSheet); closeSheet(ingBackdrop, ingSheet);
}); });
const closeCopy = () => closeSheet(copyBackdrop, copySheet);
bindPlannerSheetDragClose(copySheet, closeCopy);
copyBackdrop?.addEventListener('click', closeCopy);
document.getElementById('planner-copy-day')?.addEventListener('click', () => {
const srcKey = dateKey(state.selected);
const srcPlan = state.plans[srcKey];
if (!srcPlan || Object.keys(srcPlan).length === 0) {
showPlannerToast('Ten dzień jest pusty — nie ma co kopiować.');
return;
}
const copyList = document.getElementById('planner-copy-list');
if (!copyList) return;
const days = [];
for (let i = -3; i <= 10; i++) {
if (i === 0) continue;
const d = addDays(state.selected, i);
days.push(d);
}
copyList.innerHTML = days.map((d) => {
const wd = WEEKDAYS_LONG[d.getDay()];
const label = `${wd}, ${d.getDate()} ${CALENDAR_MONTHS_SHORT[d.getMonth()]}`;
const hasMeals = dayHasAnyMeal(state.plans, d);
const badge = hasMeals ? '<span class="text-[10px] text-amber-600 font-semibold">ma posiłki</span>' : '';
return `<button type="button" class="planner-copy-target w-full flex items-center justify-between gap-2 p-3 rounded-xl border border-gray-200 bg-gray-50/80 hover:border-gray-900 hover:bg-white transition-all text-left" data-target-ts="${d.getTime()}">
<span class="text-[13px] font-semibold text-gray-900">${escapeHtml(label)}</span>
${badge}
</button>`;
}).join('');
openSheet(copyBackdrop, copySheet);
});
document.getElementById('planner-copy-list')?.addEventListener('click', (e) => {
const btn = e.target.closest('.planner-copy-target');
if (!btn) return;
const targetDate = new Date(Number(btn.getAttribute('data-target-ts')));
const srcKey = dateKey(state.selected);
const tgtKey = dateKey(targetDate);
const srcPlan = state.plans[srcKey];
if (!srcPlan) return;
const copy = {};
for (const [slotId, entries] of Object.entries(srcPlan)) {
if (slotId === '_skipped') {
copy._skipped = { ...entries };
continue;
}
if (Array.isArray(entries)) {
copy[slotId] = entries.map((e) => ({ ...e, id: newPlanEntryId() }));
}
}
state.plans[tgtKey] = copy;
closeCopy();
persist();
showPlannerToast('Plan skopiowany!');
});
ingSheet?.addEventListener('click', (e) => { ingSheet?.addEventListener('click', (e) => {
const row = e.target.closest('.planner-ing-row'); const row = e.target.closest('.planner-ing-row');
if (!row || !ingSheet.contains(row)) return; if (!row || !ingSheet.contains(row)) return;