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

@@ -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('');